From 91f78ecd0f3ff7a2ae225a835b3c87b8a7224fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Tue, 5 May 2020 14:16:22 -0700 Subject: [PATCH 001/478] added "whitelist" under [traffic] --- cfg/conf.sample.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index d2d285d..e2d7fec 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -127,6 +127,10 @@ markdown = "Markdown" ; Set this to 0 to disable rate limiting. limit = 10 +; (optional) if you only want some source IP addresses to create pastes +; enter their IPv4 address(es) here, separated by commas +; whitelist = "12.34.56.78,99.88.77.66" + ; (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" From 5644001c5377b5a0791e136cb67436a77490db6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Tue, 5 May 2020 14:17:15 -0700 Subject: [PATCH 002/478] added "whitelist" under [traffic] --- lib/Configuration.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Configuration.php b/lib/Configuration.php index 06edf68..aa6d15d 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -78,6 +78,7 @@ class Configuration ), 'traffic' => array( 'limit' => 10, + 'whitelist' => null, 'header' => null, 'dir' => 'data', ), From 9327c9b58bc70709ce27ca30d29f63e4d801bd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Tue, 5 May 2020 14:18:52 -0700 Subject: [PATCH 003/478] added whitelist check --- lib/Controller.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/Controller.php b/lib/Controller.php index 21a27b2..5db14c2 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -196,6 +196,19 @@ class Controller */ private function _create() { + // Check whitelist if allowed to create + $whitelist = explode(',', $this->_conf->getKey('whitelist', 'traffic')); + if (($option = $this->_conf->getKey('header', 'traffic')) !== null) { + $httpHeader = 'HTTP_' . $option; + if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) { + $remoteip = $_SERVER[$httpHeader]; + } + } + if( !in_array($remoteip, $whitelist) ) { + $this->_return_message(1, I18n::_('Your IP is not authorized')); + return; + } + // Ensure last paste from visitors IP address was more than configured amount of seconds ago. TrafficLimiter::setConfiguration($this->_conf); if (!TrafficLimiter::canPass()) { From 9ca041fa068e9ecb30a8924b91778ffc9f9c396a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Thu, 7 May 2020 15:53:56 -0700 Subject: [PATCH 004/478] Update lib/Controller.php Co-authored-by: rugk --- lib/Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Controller.php b/lib/Controller.php index 5db14c2..c202f39 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -205,7 +205,7 @@ class Controller } } if( !in_array($remoteip, $whitelist) ) { - $this->_return_message(1, I18n::_('Your IP is not authorized')); + $this->_return_message(1, I18n::_('Your IP is not authorized to create pastes.')); return; } From ef9780707a941780bf13caa29e9ef28858f4b56c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Thu, 7 May 2020 15:54:13 -0700 Subject: [PATCH 005/478] Update lib/Controller.php Co-authored-by: rugk --- lib/Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Controller.php b/lib/Controller.php index c202f39..4f3cbdf 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -204,7 +204,7 @@ class Controller $remoteip = $_SERVER[$httpHeader]; } } - if( !in_array($remoteip, $whitelist) ) { + if(!in_array($remoteip, $whitelist)) { $this->_return_message(1, I18n::_('Your IP is not authorized to create pastes.')); return; } From cea96ee12a1e3e91c72f6efe6cac636cca6a80a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Thu, 7 May 2020 15:55:09 -0700 Subject: [PATCH 006/478] Update cfg/conf.sample.php Co-authored-by: rugk --- cfg/conf.sample.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index e2d7fec..52f152e 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -129,7 +129,7 @@ limit = 10 ; (optional) if you only want some source IP addresses to create pastes ; enter their IPv4 address(es) here, separated by commas -; whitelist = "12.34.56.78,99.88.77.66" +; whitelist_paste_creation = "12.34.56.78,99.88.77.66" ; (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 From 819d25a74cfdde26be18d592e827ab24e49e2b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Thu, 7 May 2020 16:13:25 -0700 Subject: [PATCH 007/478] change to whitelist_paste_creation --- lib/Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Controller.php b/lib/Controller.php index 4f3cbdf..45b6dac 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -197,7 +197,7 @@ class Controller private function _create() { // Check whitelist if allowed to create - $whitelist = explode(',', $this->_conf->getKey('whitelist', 'traffic')); + $whitelist = explode(',', $this->_conf->getKey('whitelist_paste_creation', 'traffic')); if (($option = $this->_conf->getKey('header', 'traffic')) !== null) { $httpHeader = 'HTTP_' . $option; if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) { From c152f85b50cf38c83562bd9e7dfde543cce0d49e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Thu, 7 May 2020 16:45:24 -0700 Subject: [PATCH 008/478] removed $remoteip that the audit didn't like --- lib/Controller.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Controller.php b/lib/Controller.php index 45b6dac..00bd981 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -201,13 +201,13 @@ class Controller if (($option = $this->_conf->getKey('header', 'traffic')) !== null) { $httpHeader = 'HTTP_' . $option; if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) { - $remoteip = $_SERVER[$httpHeader]; + // compare source IP from web server with whitelist + if(!in_array($_SERVER[$httpHeader], $whitelist)) { + $this->_return_message(1, I18n::_('Your IP is not authorized to create pastes.')); + return; + } } } - if(!in_array($remoteip, $whitelist)) { - $this->_return_message(1, I18n::_('Your IP is not authorized to create pastes.')); - return; - } // Ensure last paste from visitors IP address was more than configured amount of seconds ago. TrafficLimiter::setConfiguration($this->_conf); From d847e2fcf22d72fe62b9e9c78972bf20c7b85f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Thu, 7 May 2020 16:46:31 -0700 Subject: [PATCH 009/478] alignment --- lib/Configuration.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Configuration.php b/lib/Configuration.php index aa6d15d..95f5425 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -77,10 +77,10 @@ class Configuration 'markdown' => 'Markdown', ), 'traffic' => array( - 'limit' => 10, + 'limit' => 10, 'whitelist' => null, - 'header' => null, - 'dir' => 'data', + 'header' => null, + 'dir' => 'data', ), 'purge' => array( 'limit' => 300, From b8594c174a1027bb6cd449e4cb50f99617055ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Thu, 7 May 2020 16:48:17 -0700 Subject: [PATCH 010/478] whitelist_paste_creation description --- cfg/conf.sample.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index 52f152e..6b266a7 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -128,7 +128,8 @@ markdown = "Markdown" limit = 10 ; (optional) if you only want some source IP addresses to create pastes -; enter their IPv4 address(es) here, separated by commas +; enter their IPv4 address(es) here, separated by commas. This does not +; currently support CIDR notation, only individual IPv4 addresses. ; whitelist_paste_creation = "12.34.56.78,99.88.77.66" ; (optional) if your website runs behind a reverse proxy or load balancer, From 8fbdb69d8a2daf48c96ce39bc91bafbc2febb451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Fri, 8 May 2020 11:36:19 -0700 Subject: [PATCH 011/478] added check for null whitelist --- lib/Controller.php | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/Controller.php b/lib/Controller.php index 00bd981..6b1dbcb 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -196,16 +196,21 @@ class Controller */ private function _create() { - // Check whitelist if allowed to create - $whitelist = explode(',', $this->_conf->getKey('whitelist_paste_creation', 'traffic')); - if (($option = $this->_conf->getKey('header', 'traffic')) !== null) { - $httpHeader = 'HTTP_' . $option; - if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) { - // compare source IP from web server with whitelist - if(!in_array($_SERVER[$httpHeader], $whitelist)) { - $this->_return_message(1, I18n::_('Your IP is not authorized to create pastes.')); - return; - } + // Check if whitelist feature is enabled + if (($option = $this->_conf->getKey('whitelist', 'traffic')) !== null) { + // Parse whitelist into array + $whitelist = explode(',', $this->_conf->getKey('whitelist_paste_creation', 'traffic')); + // Check for source IP in HTTP header + if (($option = $this->_conf->getKey('header', 'traffic')) !== null) { + $httpHeader = 'HTTP_' . $option; + // Grab source IP from HTTP header (if it exists) + if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) { + // Check if source IP reported from HTTP header is in whitelist array + if (!in_array($_SERVER[$httpHeader], $whitelist)) { + $this->_return_message(1, I18n::_('Your IP is not authorized to create pastes.')); + return; + } + } } } From effe6ad3e55201d7999c20873a28ab1bba8bc3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Fri, 8 May 2020 11:37:21 -0700 Subject: [PATCH 012/478] fixed spacing to please StyleCI --- lib/Controller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Controller.php b/lib/Controller.php index 6b1dbcb..2c08b30 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -209,11 +209,11 @@ class Controller if (!in_array($_SERVER[$httpHeader], $whitelist)) { $this->_return_message(1, I18n::_('Your IP is not authorized to create pastes.')); return; - } + } } } } - + // Ensure last paste from visitors IP address was more than configured amount of seconds ago. TrafficLimiter::setConfiguration($this->_conf); if (!TrafficLimiter::canPass()) { From 3f75c81a2feaa1c2350f2d674c86e5a0dc936471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Andr=C3=A9s?= Date: Fri, 8 May 2020 12:18:20 -0700 Subject: [PATCH 013/478] fixed duplicated getKey() --- lib/Controller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Controller.php b/lib/Controller.php index 2c08b30..0aa3fe1 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -197,9 +197,9 @@ class Controller private function _create() { // Check if whitelist feature is enabled - if (($option = $this->_conf->getKey('whitelist', 'traffic')) !== null) { + if (($option = $this->_conf->getKey('whitelist_paste_creation', 'traffic')) !== null) { // Parse whitelist into array - $whitelist = explode(',', $this->_conf->getKey('whitelist_paste_creation', 'traffic')); + $whitelist = explode(',', $option); // Check for source IP in HTTP header if (($option = $this->_conf->getKey('header', 'traffic')) !== null) { $httpHeader = 'HTTP_' . $option; From 8a08a2167bfde6b62aac20c23941cc054a8d364b Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 6 Apr 2021 06:27:12 +0200 Subject: [PATCH 014/478] fix display of indonesian label in drop-down --- i18n/languages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/languages.json b/i18n/languages.json index 6a83544..a2aff4a 100644 --- a/i18n/languages.json +++ b/i18n/languages.json @@ -63,10 +63,10 @@ "ho": ["Hiri Motu", "Hiri Motu"], "hu": ["magyar", "Hungarian"], "ia": ["Interlingua", "Interlingua"], + "id": ["bahasa Indonesia","Indonesian"], "ie": ["Interlingue", "Interlingue"], "ga": ["Gaeilge", "Irish"], "ig": ["Asụsụ Igbo", "Igbo"], - "in": ["bahasa Indonesia","Indonesian"], "ik": ["Iñupiaq", "Inupiaq"], "io": ["Ido", "Ido"], "is": ["Íslenska", "Icelandic"], From 553417194cda90149e1d35da5b0830412ba51607 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Tue, 6 Apr 2021 20:07:13 +0200 Subject: [PATCH 015/478] New translations en.json (Estonian) --- i18n/et.json | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 i18n/et.json diff --git a/i18n/et.json b/i18n/et.json new file mode 100644 index 0000000..295f512 --- /dev/null +++ b/i18n/et.json @@ -0,0 +1,188 @@ +{ + "PrivateBin": "PrivateBin", + "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", + "More information on the 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.", + "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", + "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", + "Please wait %d seconds between each post.": [ + "Please wait %d second between each post. (singular)", + "Please wait %d seconds between each post. (1st plural)", + "Please wait %d seconds between each post. (2nd plural)", + "Please wait %d seconds between each post. (3rd plural)" + ], + "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", + "Invalid data.": "Invalid data.", + "You are unlucky. Try again.": "You are unlucky. Try again.", + "Error saving comment. Sorry.": "Error saving comment. Sorry.", + "Error saving paste. Sorry.": "Error saving paste. Sorry.", + "Invalid paste ID.": "Invalid paste ID.", + "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", + "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", + "Paste was properly deleted.": "Paste was properly deleted.", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", + "%s requires a modern browser to work.": "%s requires a modern browser to work.", + "New": "New", + "Send": "Send", + "Clone": "Clone", + "Raw text": "Raw text", + "Expires": "Expires", + "Burn after reading": "Burn after reading", + "Open discussion": "Open discussion", + "Password (recommended)": "Password (recommended)", + "Discussion": "Discussion", + "Toggle navigation": "Toggle navigation", + "%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." +} From 587822838a94bd4f7a038af4305deb919245ae86 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Wed, 7 Apr 2021 09:18:03 +0200 Subject: [PATCH 016/478] New translations en.json (Chinese Simplified) --- i18n/zh.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/zh.json b/i18n/zh.json index 67a456f..2e1dc12 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -111,7 +111,7 @@ "Please enter the password for this paste:": "请输入这份粘贴内容的密码:", "Could not decrypt data (Wrong key?)": "无法解密数据(密钥错误?)", "Could not delete the paste, it was not stored in burn after reading mode.": "无法删除此粘贴内容,它没有以阅后即焚模式保存。", - "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "注意啦!!!不要关闭窗口,否则你再也见不到这条消息了。", + "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "看!仔!细!了!不要关闭窗口,否则你再也见不到这条消息了。", "Could not decrypt comment; Wrong key?": "无法解密评论; 密钥错误?", "Reply": "回复", "Anonymous": "匿名", @@ -162,7 +162,7 @@ "Loading…": "载入中…", "Decrypting 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.": "如果这个消息一直存在,请参考 这里的 FAQ (英文版)进行故障排除。", "+++ no paste text +++": "+++ 没有粘贴内容 +++", "Could not get paste data: %s": "无法获取粘贴数据:%s", "QR code": "二维码", @@ -184,5 +184,5 @@ "Close": "关闭", "Encrypted note on PrivateBin": "PrivateBin上的加密笔记", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "访问这个链接来查看该笔记。 将这个URL发送给任何人即可允许其访问该笔记。", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL 缩短可能会暴露您在 URL 中的解密密钥。" } From f5fa37b5f28ac554a50c60d8afcfb337c827315f Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 8 Apr 2021 20:55:45 +0200 Subject: [PATCH 017/478] New translations en.json (Estonian) --- i18n/et.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i18n/et.json b/i18n/et.json index 295f512..490d97e 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -1,9 +1,9 @@ { "PrivateBin": "PrivateBin", - "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", - "More information on the project page.": "More information on the project page.", - "Because ignorance is bliss": "Because ignorance is bliss", - "en": "en", + "%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.": "Paste does not exist, has expired or has been deleted.", "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", From e50f3eb31102c4df9f29120f63d2a87f861bd610 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 8 Apr 2021 22:00:09 +0200 Subject: [PATCH 018/478] New translations en.json (Estonian) --- i18n/et.json | 228 +++++++++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/i18n/et.json b/i18n/et.json index 490d97e..d5b9aa4 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -4,132 +4,132 @@ "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.": "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.", + "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 php %s-d või kõrgemat, et töötada. Vabandame.", "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", "Please wait %d seconds between each post.": [ - "Please wait %d second between each post. (singular)", - "Please wait %d seconds between each post. (1st plural)", - "Please wait %d seconds between each post. (2nd plural)", - "Please wait %d seconds between each post. (3rd plural)" + "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.": "Paste is limited to %s of encrypted data.", - "Invalid data.": "Invalid data.", - "You are unlucky. Try again.": "You are unlucky. Try again.", - "Error saving comment. Sorry.": "Error saving comment. Sorry.", - "Error saving paste. Sorry.": "Error saving paste. Sorry.", - "Invalid paste ID.": "Invalid paste ID.", + "Paste is limited to %s of encrypted data.": "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.": "Paste is not of burn-after-reading type.", - "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", - "Paste was properly deleted.": "Paste was properly deleted.", - "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", - "%s requires a modern browser to work.": "%s requires a modern browser to work.", - "New": "New", - "Send": "Send", - "Clone": "Clone", - "Raw text": "Raw text", - "Expires": "Expires", - "Burn after reading": "Burn after reading", - "Open discussion": "Open discussion", - "Password (recommended)": "Password (recommended)", - "Discussion": "Discussion", + "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": "Toggle navigation", "%d seconds": [ - "%d second (singular)", - "%d seconds (1st plural)", - "%d seconds (2nd plural)", - "%d seconds (3rd plural)" + "%d sekund", + "%d sekundit", + "%d sekundit", + "%d sekundit" ], "%d minutes": [ - "%d minute (singular)", - "%d minutes (1st plural)", - "%d minutes (2nd plural)", - "%d minutes (3rd plural)" + "%d minut", + "%d minutit", + "%d minutit", + "%d minutit" ], "%d hours": [ - "%d hour (singular)", - "%d hours (1st plural)", - "%d hours (2nd plural)", - "%d hours (3rd plural)" + "%d tund", + "%d tundi", + "%d tundi", + "%d tundi" ], "%d days": [ - "%d day (singular)", - "%d days (1st plural)", - "%d days (2nd plural)", - "%d days (3rd plural)" + "%d päev", + "%d päeva", + "%d päeva", + "%d päeva" ], "%d weeks": [ - "%d week (singular)", - "%d weeks (1st plural)", - "%d weeks (2nd plural)", - "%d weeks (3rd plural)" + "%d nädal", + "%d nädalat", + "%d nädalat", + "%d nädalat" ], "%d months": [ - "%d month (singular)", - "%d months (1st plural)", - "%d months (2nd plural)", - "%d months (3rd plural)" + "%d kuu", + "%d kuud", + "%d kuud", + "%d kuud" ], "%d years": [ - "%d year (singular)", - "%d years (1st plural)", - "%d years (2nd plural)", - "%d years (3rd plural)" + "%d aasta", + "%d aastat", + "%d aastat", + "%d aastat" ], - "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.", + "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 hukkuvad, kui seda teenust kuritarvitad.", "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)" + "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.": [ - "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)" + "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.": [ - "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)" + "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.": [ - "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)" + "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.": [ - "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)" + "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:": "Please enter the password for this paste:", + "Please enter the password for this paste:": "Palun sisesta selle kleepe parool:", "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", + "Reply": "Vasta", + "Anonymous": "Anonüümne", "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.", + "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": "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?)", + "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", @@ -139,49 +139,49 @@ "EiB": "EiB", "ZiB": "ZiB", "YiB": "YiB", - "Format": "Format", - "Plain Text": "Plain Text", - "Source Code": "Source Code", + "Format": "Formaat", + "Plain Text": "Lihttekst", + "Source Code": "Lähtekood", "Markdown": "Markdown", - "Download attachment": "Download attachment", - "Cloned: '%s'": "Cloned: '%s'", + "Download attachment": "Laadi manus alla", + "Cloned: '%s'": "Kloonitud: '%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", + "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-i", + "Editor": "Toimetaja", + "Preview": "Eelvaade", "%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…", + "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.": "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 kood", "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": "Proovi uuesti", "Showing raw text…": "Showing raw text…", - "Notice:": "Notice:", + "Notice:": "Teade:", "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", + "Close": "Sulge", "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." From 727166e945ac5cb2d8cce7b5b0fd605d38a7ab6a Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 8 Apr 2021 23:05:35 +0200 Subject: [PATCH 019/478] New translations en.json (Estonian) --- i18n/et.json | 66 ++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/i18n/et.json b/i18n/et.json index d5b9aa4..877c732 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -5,8 +5,8 @@ "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 php %s-d või kõrgemat, et töötada. Vabandame.", - "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", + "%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.", @@ -19,7 +19,7 @@ "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.": "Paste is not of burn-after-reading type.", + "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.", @@ -33,7 +33,7 @@ "Open discussion": "Avatud arutelu", "Password (recommended)": "Parool (soovitatav)", "Discussion": "Arutelu", - "Toggle navigation": "Toggle navigation", + "Toggle navigation": "Näita menüüd", "%d seconds": [ "%d sekund", "%d sekundit", @@ -77,7 +77,7 @@ "%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 hukkuvad, kui seda teenust kuritarvitad.", + "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.", @@ -109,20 +109,20 @@ "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?)": "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?", + "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 generated from IP address", + "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": "Could not refresh display: %s", - "unknown status": "unknown status", + "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…", @@ -145,7 +145,7 @@ "Markdown": "Markdown", "Download attachment": "Laadi manus alla", "Cloned: '%s'": "Kloonitud: '%s'", - "The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.", + "The cloned file '%s' was attached to this paste.": "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.", @@ -153,36 +153,36 @@ "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-i", + "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 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.": "%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.": "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", + "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.": "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.", + "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…": "Showing raw text…", + "Showing raw text…": "Lähteteksti näitamine…", "Notice:": "Teade:", - "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.", + "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?": "Recipient may become aware of your timezone, convert time to UTC?", - "Use Current Timezone": "Use Current Timezone", - "Convert To UTC": "Convert To UTC", + "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": "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." + "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." } From 1ff8637c237e7b645e78a7a9239a720c544d5257 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sat, 10 Apr 2021 15:45:21 +0200 Subject: [PATCH 020/478] New translations en.json (Lithuanian) --- i18n/lt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/lt.json b/i18n/lt.json index f973f4f..afbf111 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -184,5 +184,5 @@ "Close": "Užverti", "Encrypted note on PrivateBin": "Šifruoti užrašai ties PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Norėdami matyti užrašus, aplankykite šį tinklalapį. Pasidalinus šiuo URL adresu su kitais žmonėmis, jiems taip pat bus leidžiama prieiga prie šių užrašų.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL trumpinimo įrankis gali atskleisti URL adrese jūsų iššifravimo raktą." } From ab250d8686195d65a680fb1cde81e4ccdc64be0d Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sat, 10 Apr 2021 16:52:48 +0200 Subject: [PATCH 021/478] New translations en.json (Lithuanian) --- i18n/lt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/lt.json b/i18n/lt.json index afbf111..7ef45bf 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -1,7 +1,7 @@ { "PrivateBin": "PrivateBin", "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s yra minimalistinis, atvirojo kodo internetinis įdėjimų dėklas, kurį naudojant, serveris nieko nenutuokia apie įdėtus duomenis. Duomenys yra šifruojami/iššifruojami %snaršyklėje%s naudojant 256 bitų AES.", - "More information on the project page.": "Daugiau informacijos rasite projeketo puslapyje.", + "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.", From 175d14224eeb5f43108d2bd4a6442ade2290b70e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Fri, 16 Apr 2021 18:27:12 +0200 Subject: [PATCH 022/478] set plurals for and credit Estonian translation --- CHANGELOG.md | 1 + CREDITS.md | 1 + js/privatebin.js | 6 +++--- lib/I18n.php | 4 ++-- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 201fb81..a67fdc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # PrivateBin version history * **1.4 (not yet released)** + * ADDED: Translation for Estonian * **1.3.5 (2021-04-05)** * ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan * ADDED: Make the project info configurable (#681) diff --git a/CREDITS.md b/CREDITS.md index 8b24f16..1aabf6f 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -50,3 +50,4 @@ Sébastien Sauvage - original idea and main developer * Moo - Lithuanian * whenwesober - Indonesian * retiolus - Catalan +* sarnane - Estonian diff --git a/js/privatebin.js b/js/privatebin.js index 42cdd96..e549dd6 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -601,7 +601,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @prop {string[]} * @readonly */ - const supportedLanguages = ['bg', 'cs', 'de', 'es', 'fr', 'he', 'hu', 'it', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh']; + const supportedLanguages = ['bg', 'ca', 'cs', 'de', 'es', 'et', 'fr', 'he', 'hu', 'id', 'it', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh']; /** * built in language @@ -767,7 +767,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { /** * per language functions to use to determine the plural form * - * @see {@link http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html} + * @see {@link https://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html} * @name I18n.getPluralForm * @function * @param {int} n @@ -795,7 +795,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { return n % 10 === 1 && n % 100 !== 11 ? 0 : (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); case 'sl': return n % 100 === 1 ? 1 : (n % 100 === 2 ? 2 : (n % 100 === 3 || n % 100 === 4 ? 3 : 0)); - // bg, ca, de, en, es, hu, it, nl, no, pt + // bg, ca, de, en, es, et, hu, it, nl, no, pt default: return n !== 1 ? 1 : 0; } diff --git a/lib/I18n.php b/lib/I18n.php index ea7d07f..50bf0cc 100644 --- a/lib/I18n.php +++ b/lib/I18n.php @@ -305,7 +305,7 @@ class I18n /** * determines the plural form to use based on current language and given number * - * From: http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html + * From: https://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html * * @access protected * @static @@ -334,7 +334,7 @@ class I18n return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); case 'sl': return $n % 100 == 1 ? 1 : ($n % 100 == 2 ? 2 : ($n % 100 == 3 || $n % 100 == 4 ? 3 : 0)); - // bg, ca, de, en, es, hu, it, nl, no, pt + // bg, ca, de, en, es, et, hu, it, nl, no, pt default: return $n != 1 ? 1 : 0; } diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index a86faf3..ffea872 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -72,7 +72,7 @@ endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index 6dd4331..e58617d 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ endif; ?> - + From 9e6eb50cede0bb27c4fa9a8e2d7d11961708be46 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Fri, 16 Apr 2021 19:19:11 +0200 Subject: [PATCH 023/478] adding new security headers, fixes #765 --- CHANGELOG.md | 1 + lib/Controller.php | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a67fdc1..7276c84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * **1.4 (not yet released)** * ADDED: Translation for Estonian + * ADDED: new HTTP headers improving security (#765) * **1.3.5 (2021-04-05)** * ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan * ADDED: Make the project info configurable (#681) diff --git a/lib/Controller.php b/lib/Controller.php index 38fc09d..bc23a52 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -346,10 +346,13 @@ 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('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(); From ed663513372385754b37c57f0a7bee41b1c79a08 Mon Sep 17 00:00:00 2001 From: Christian Pierre MOMON Date: Wed, 14 Apr 2021 03:11:58 +0200 Subject: [PATCH 024/478] Added download feature (#5318). --- css/privatebin.css | 4 ++++ js/privatebin.js | 41 +++++++++++++++++++++++++++++++++++++++++ tpl/bootstrap.php | 5 ++++- tpl/page.php | 2 +- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/css/privatebin.css b/css/privatebin.css index 8b4e6a0..f852d39 100644 --- a/css/privatebin.css +++ b/css/privatebin.css @@ -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/js/privatebin.js b/js/privatebin.js index e549dd6..5090885 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -3525,6 +3525,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $password, $passwordInput, $rawTextButton, + $downloadTextButton, $qrCodeLink, $emailLink, $sendButton, @@ -3666,6 +3667,30 @@ jQuery.PrivateBin = (function($, RawDeflate) { newDoc.close(); } + /** + * download text + * + * @name TopNav.downloadText + * @private + * @function + */ + function downloadText() + { + var filename='paste.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 * @@ -3892,6 +3917,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $newButton.removeClass('hidden'); $cloneButton.removeClass('hidden'); $rawTextButton.removeClass('hidden'); + $downloadTextButton.removeClass('hidden'); $qrCodeLink.removeClass('hidden'); viewButtonsDisplayed = true; @@ -3912,6 +3938,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $cloneButton.addClass('hidden'); $newButton.addClass('hidden'); $rawTextButton.addClass('hidden'); + $downloadTextButton.addClass('hidden'); $qrCodeLink.addClass('hidden'); me.hideEmailButton(); @@ -4073,6 +4100,17 @@ jQuery.PrivateBin = (function($, RawDeflate) { $rawTextButton.addClass('hidden'); }; + /** + * only hides the download text button + * + * @name TopNav.hideRawButton + * @function + */ + me.hideDownloadButton = function() + { + $downloadTextButton.addClass('hidden'); + }; + /** * only hides the qr code button * @@ -4334,6 +4372,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $password = $('#password'); $passwordInput = $('#passwordinput'); $rawTextButton = $('#rawtextbutton'); + $downloadTextButton = $('#downloadtextbutton'); $retryButton = $('#retrybutton'); $sendButton = $('#sendbutton'); $qrCodeLink = $('#qrcodelink'); @@ -4351,6 +4390,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $sendButton.click(PasteEncrypter.sendPaste); $cloneButton.click(Controller.clonePaste); $rawTextButton.click(rawText); + $downloadTextButton.click(downloadText); $retryButton.click(clickRetryButton); $fileRemoveButton.click(removeAttachment); $qrCodeLink.click(displayQrCode); @@ -4689,6 +4729,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { TopNav.showEmailButton(); TopNav.hideRawButton(); + TopNav.hideDownloadButton(); Editor.hide(); // parse and show text diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index ffea872..c43ddbf 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -72,7 +72,7 @@ endif; ?> - + @@ -212,6 +212,9 @@ endif; + diff --git a/tpl/page.php b/tpl/page.php index e58617d..096a9e6 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ endif; ?> - + From 1dc8b24665bfe65ef6696468235f5698e4195899 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Fri, 16 Apr 2021 20:15:12 +0200 Subject: [PATCH 025/478] transmit cookie only over HTTPS, fixes #472 --- CHANGELOG.md | 1 + js/privatebin.js | 2 +- lib/Controller.php | 4 ++-- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7276c84..00f5f21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * **1.4 (not yet released)** * ADDED: Translation for Estonian * ADDED: new HTTP headers improving security (#765) + * CHANGED: Language selection cookie only transmitted over HTTPS (#472) * **1.3.5 (2021-04-05)** * ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan * ADDED: Make the project info configurable (#681) diff --git a/js/privatebin.js b/js/privatebin.js index e549dd6..0519970 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -3676,7 +3676,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(); } diff --git a/lib/Controller.php b/lib/Controller.php index bc23a52..5b81cd8 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -170,7 +170,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); } } @@ -367,7 +367,7 @@ class Controller $languageselection = ''; if ($this->_conf->getKey('languageselection')) { $languageselection = I18n::getLanguage(); - setcookie('lang', $languageselection); + setcookie('lang', $languageselection, 0, '', '', true); } $page = new View; diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index ffea872..f8f9444 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -72,7 +72,7 @@ endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index e58617d..a227232 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ endif; ?> - + From 6f3bb25b092cf33160d1a8d2071f7ca4e5cedcaa Mon Sep 17 00:00:00 2001 From: El RIDO Date: Fri, 16 Apr 2021 20:25:50 +0200 Subject: [PATCH 026/478] disable Google FloC --- lib/Controller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Controller.php b/lib/Controller.php index bc23a52..bfa29b1 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -349,6 +349,7 @@ class Controller 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-Content-Type-Options: nosniff'); header('X-Frame-Options: deny'); From fd7d05e8624b9a8293496ace80ed437ccd02f88d Mon Sep 17 00:00:00 2001 From: rugk Date: Fri, 16 Apr 2021 22:03:02 +0200 Subject: [PATCH 027/478] Add base URL as default CSP restriction This follows an [HTTP Observatory recommendation](https://observatory.mozilla.org/analyze/privatebin.net): > Restricts use of the tag by using base-uri 'none', base-uri 'self', or specific origins. Given we don't use that anywhere, this safe should be safe. (not tested practically though) --- cfg/conf.sample.php | 2 +- lib/Configuration.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index e958c88..570503c 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -87,7 +87,7 @@ languageselection = false ; async functions and display an error if not and for Chrome to enable ; webassembly support (used for zlib compression). You can remove it if Chrome ; doesn't need to be supported and old browsers don't need to be warned. -; cspheader = "default-src 'none'; manifest-src 'self'; connect-src * blob:; script-src 'self' 'unsafe-eval' resource:; style-src 'self'; font-src 'self'; img-src 'self' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads" +; cspheader = "default-src 'none'; base-uri 'self'; 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 diff --git a/lib/Configuration.php b/lib/Configuration.php index 2a326ca..741ab7c 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -55,7 +55,7 @@ class Configuration 'urlshortener' => '', 'qrcode' => true, 'icon' => 'identicon', - 'cspheader' => 'default-src \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\' resource:; style-src \'self\'; font-src \'self\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads', + 'cspheader' => 'default-src \'none\'; base-uri \'self\'; 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', @@ -265,3 +265,4 @@ class Configuration return $this->_configuration[$section]; } } + From 7b7a32c0a734b53ca2c7276410759390ea308189 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 17 Apr 2021 08:20:08 +0200 Subject: [PATCH 028/478] apply StyleCI recommendation --- lib/Configuration.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Configuration.php b/lib/Configuration.php index 741ab7c..426cd15 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -265,4 +265,3 @@ class Configuration return $this->_configuration[$section]; } } - From 5f4200c721be070162981878dff30ade1bcde89f Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 17 Apr 2021 08:39:35 +0200 Subject: [PATCH 029/478] document change --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00f5f21..b327893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * **1.4 (not yet released)** * ADDED: Translation for Estonian * ADDED: new HTTP headers improving security (#765) + * ADDED: Opt-out of federated learning of cohorts (FLoC) (#776) * CHANGED: Language selection cookie only transmitted over HTTPS (#472) * **1.3.5 (2021-04-05)** * ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan From 9683c591bb77f57444ea6425cc37da09950a8662 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 17 Apr 2021 08:48:12 +0200 Subject: [PATCH 030/478] document change --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b327893..8e416b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * **1.4 (not yet released)** * ADDED: Translation for Estonian * ADDED: new HTTP headers improving security (#765) + * ADDED: Download button for paste text (#774) * ADDED: Opt-out of federated learning of cohorts (FLoC) (#776) * CHANGED: Language selection cookie only transmitted over HTTPS (#472) * **1.3.5 (2021-04-05)** From 853a4f386fa9a870cb2aa3616d43b61d0f10ec5f Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 17 Apr 2021 08:51:25 +0200 Subject: [PATCH 031/478] fix indentation --- js/privatebin.js | 24 ++++++++++++------------ tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 0fe0e1d..9cf569f 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -3525,7 +3525,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { $password, $passwordInput, $rawTextButton, - $downloadTextButton, + $downloadTextButton, $qrCodeLink, $emailLink, $sendButton, @@ -3676,20 +3676,20 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ function downloadText() { - var filename='paste.txt'; - var text = PasteViewer.getText(); + var filename='paste.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); + 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.style.display = 'none'; + document.body.appendChild(element); - element.click(); + element.click(); - document.body.removeChild(element); - } + document.body.removeChild(element); + } /** * saves the language in a cookie and reloads the page @@ -4729,7 +4729,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { TopNav.showEmailButton(); TopNav.hideRawButton(); - TopNav.hideDownloadButton(); + TopNav.hideDownloadButton(); Editor.hide(); // parse and show text diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 38cedc1..ae4d1e7 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -72,7 +72,7 @@ endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index 832e6da..724c272 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ endif; ?> - + From bc114522592f0c3ed2d366363ad264437cf904aa Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 17 Apr 2021 09:08:11 +0200 Subject: [PATCH 032/478] make filename unique per paste ID --- js/privatebin.js | 2 +- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 9cf569f..6218700 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -3676,7 +3676,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ function downloadText() { - var filename='paste.txt'; + var filename='paste-' + Model.getPasteId() + '.txt'; var text = PasteViewer.getText(); var element = document.createElement('a'); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index ae4d1e7..fd715c4 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -72,7 +72,7 @@ endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index 724c272..53a8171 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ endif; ?> - + From 3181cfe58add27674e9e893f01b2a0fbaf2e6d4e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 17 Apr 2021 09:15:00 +0200 Subject: [PATCH 033/478] translate download button, add it to page template --- i18n/ar.json | 3 ++- i18n/bg.json | 3 ++- i18n/ca.json | 3 ++- i18n/cs.json | 3 ++- i18n/de.json | 3 ++- i18n/el.json | 3 ++- i18n/en.json | 3 ++- i18n/es.json | 3 ++- i18n/et.json | 3 ++- i18n/fr.json | 3 ++- i18n/he.json | 3 ++- i18n/hi.json | 3 ++- i18n/hu.json | 3 ++- i18n/id.json | 3 ++- i18n/it.json | 3 ++- i18n/ja.json | 3 ++- i18n/ku.json | 3 ++- i18n/la.json | 3 ++- i18n/lt.json | 3 ++- i18n/nl.json | 3 ++- i18n/no.json | 3 ++- i18n/oc.json | 3 ++- i18n/pl.json | 3 ++- i18n/pt.json | 3 ++- i18n/ru.json | 3 ++- i18n/sl.json | 3 ++- i18n/sv.json | 3 ++- i18n/tr.json | 3 ++- i18n/uk.json | 3 ++- i18n/zh.json | 3 ++- tpl/bootstrap.php | 2 +- tpl/page.php | 1 + 32 files changed, 62 insertions(+), 31 deletions(-) diff --git a/i18n/ar.json b/i18n/ar.json index 8b8e1ae..894b87b 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/bg.json b/i18n/bg.json index 8ddd1f5..1385ab1 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/ca.json b/i18n/ca.json index a4e6d3f..d81499c 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/cs.json b/i18n/cs.json index 1a6249a..956d1db 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/de.json b/i18n/de.json index dd8a48a..7cecba7 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -184,5 +184,6 @@ "Close": "Schliessen", "Encrypted note on PrivateBin": "Verschlüsselte Notiz auf PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besuche diese Verknüpfung um das Dokument zu sehen. Wird die URL an eine andere Person gegeben, so kann diese Person ebenfalls auf dieses Dokument zugreifen.", - "URL shortener may expose your decrypt key in URL.": "Der URL-Verkürzer kann den Schlüssel in der URL enthüllen." + "URL shortener may expose your decrypt key in URL.": "Der URL-Verkürzer kann den Schlüssel in der URL enthüllen.", + "Download": "Download" } diff --git a/i18n/el.json b/i18n/el.json index 6fd45e4..54f21a2 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/en.json b/i18n/en.json index 295f512..e38b40e 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/es.json b/i18n/es.json index 4e93e66..307a855 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -184,5 +184,6 @@ "Close": "Cerrar", "Encrypted note on PrivateBin": "Nota cifrada en PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite este enlace para ver la nota. Dar la URL a cualquier persona también les permite acceder a la nota.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/et.json b/i18n/et.json index 877c732..0b4d5cf 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -184,5 +184,6 @@ "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." + "URL shortener may expose your decrypt key in URL.": "URL-i lühendaja võib paljastada sinu dekrüpteerimisvõtme URL-is.", + "Download": "Download" } diff --git a/i18n/fr.json b/i18n/fr.json index 409d449..bc53fe1 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -184,5 +184,6 @@ "Close": "Fermer", "Encrypted note on PrivateBin": "Message chiffré sur PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visiter ce lien pour voir la note. Donner l'URL à une autre personne lui permet également d'accéder à la note.", - "URL shortener may expose your decrypt key in URL.": "Raccourcir l'URL peut exposer votre clé de déchiffrement dans l'URL." + "URL shortener may expose your decrypt key in URL.": "Raccourcir l'URL peut exposer votre clé de déchiffrement dans l'URL.", + "Download": "Download" } diff --git a/i18n/he.json b/i18n/he.json index 206587b..eb2d512 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -184,5 +184,6 @@ "Close": "סגירה", "Encrypted note on PrivateBin": "הערה מוצפנת ב־PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "נא לבקר בקישור כדי לצפות בהערה. מסירת הקישור לאנשים כלשהם תאפשר גם להם לגשת להערה.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/hi.json b/i18n/hi.json index a248e82..4df9ce0 100644 --- a/i18n/hi.json +++ b/i18n/hi.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/hu.json b/i18n/hu.json index b870836..a619b3f 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -184,5 +184,6 @@ "Close": "Bezárás", "Encrypted note on PrivateBin": "Titkosított jegyzet a PrivateBinen", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Látogasd meg ezt a hivatkozást a bejegyzés megtekintéséhez. Ha mások számára is megadod ezt a linket, azzal hozzáférnek ők is.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/id.json b/i18n/id.json index 248121d..1547ace 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -184,5 +184,6 @@ "Close": "Tutup", "Encrypted note on PrivateBin": "Catatan ter-ekrip di PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Kunjungi tautan ini untuk melihat catatan. Memberikan alamat URL pada siapapun juga, akan mengizinkan mereka untuk mengakses catatan, so pasti gitu loh Kaka.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/it.json b/i18n/it.json index 81b11a8..2613579 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -184,5 +184,6 @@ "Close": "Chiudi", "Encrypted note on PrivateBin": "Nota crittografata su PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visita questo collegamento per vedere la nota. Dare l'URL a chiunque consente anche a loro di accedere alla nota.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/ja.json b/i18n/ja.json index bf4f7ca..ed6971d 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/ku.json b/i18n/ku.json index 0ff29ac..0e8c299 100644 --- a/i18n/ku.json +++ b/i18n/ku.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/la.json b/i18n/la.json index 4d648cb..11d062b 100644 --- a/i18n/la.json +++ b/i18n/la.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/lt.json b/i18n/lt.json index 7ef45bf..31f7906 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -184,5 +184,6 @@ "Close": "Užverti", "Encrypted note on PrivateBin": "Šifruoti užrašai ties PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Norėdami matyti užrašus, aplankykite šį tinklalapį. Pasidalinus šiuo URL adresu su kitais žmonėmis, jiems taip pat bus leidžiama prieiga prie šių užrašų.", - "URL shortener may expose your decrypt key in URL.": "URL trumpinimo įrankis gali atskleisti URL adrese jūsų iššifravimo raktą." + "URL shortener may expose your decrypt key in URL.": "URL trumpinimo įrankis gali atskleisti URL adrese jūsų iššifravimo raktą.", + "Download": "Download" } diff --git a/i18n/nl.json b/i18n/nl.json index 54586ff..8e51dfc 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/no.json b/i18n/no.json index ac6e369..e8d9536 100644 --- a/i18n/no.json +++ b/i18n/no.json @@ -184,5 +184,6 @@ "Close": "Steng", "Encrypted note on PrivateBin": "Kryptert notat på PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besøk denne lenken for å se notatet. Hvis lenken deles med andre, vil de også kunne se notatet.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/oc.json b/i18n/oc.json index 49e3dde..1d445b1 100644 --- a/i18n/oc.json +++ b/i18n/oc.json @@ -184,5 +184,6 @@ "Close": "Tampar", "Encrypted note on PrivateBin": "Nòtas chifradas sus PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visitatz aqueste ligam per veire la nòta. Fornir lo ligam a qualqu’un mai li permet tanben d’accedir a la nòta.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/pl.json b/i18n/pl.json index c18ad22..8fde350 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/pt.json b/i18n/pt.json index 6049207..f83f778 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -184,5 +184,6 @@ "Close": "Fechar", "Encrypted note on PrivateBin": "Nota criptografada no PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite esse link para ver a nota. Dar a URL para qualquer um permite que eles também acessem a nota.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/ru.json b/i18n/ru.json index b697f70..03c4945 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -184,5 +184,6 @@ "Close": "Закрыть", "Encrypted note on PrivateBin": "Зашифрованная запись на PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Посетите эту ссылку чтобы просмотреть запись. Передача ссылки кому либо позволит им получить доступ к записи тоже.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/sl.json b/i18n/sl.json index 51b9caa..bac6f98 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/sv.json b/i18n/sv.json index e085949..a586a4f 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/tr.json b/i18n/tr.json index daedf8f..1156275 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/uk.json b/i18n/uk.json index 2808c71..3afa64d 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Download": "Download" } diff --git a/i18n/zh.json b/i18n/zh.json index 2e1dc12..65d795e 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -184,5 +184,6 @@ "Close": "关闭", "Encrypted note on PrivateBin": "PrivateBin上的加密笔记", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "访问这个链接来查看该笔记。 将这个URL发送给任何人即可允许其访问该笔记。", - "URL shortener may expose your decrypt key in URL.": "URL 缩短可能会暴露您在 URL 中的解密密钥。" + "URL shortener may expose your decrypt key in URL.": "URL 缩短可能会暴露您在 URL 中的解密密钥。", + "Download": "Download" } diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index fd715c4..6575789 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -213,7 +213,7 @@ endif; + Date: Sun, 18 Apr 2021 01:03:48 +0200 Subject: [PATCH 034/478] New translations en.json (Indonesian) --- i18n/id.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/id.json b/i18n/id.json index 248121d..a7c01b3 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -184,5 +184,5 @@ "Close": "Tutup", "Encrypted note on PrivateBin": "Catatan ter-ekrip di PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Kunjungi tautan ini untuk melihat catatan. Memberikan alamat URL pada siapapun juga, akan mengizinkan mereka untuk mengakses catatan, so pasti gitu loh Kaka.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "Pemendek URL mungkin akan menampakkan kunci dekrip Anda dalam URL." } From 0e78534e48aa0ec2075f910a847719e965247f5b Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 18 Apr 2021 09:07:57 +0200 Subject: [PATCH 035/478] re-label "Download" button to "Save paste" --- i18n/ar.json | 2 +- i18n/bg.json | 2 +- i18n/ca.json | 2 +- i18n/cs.json | 2 +- i18n/de.json | 2 +- i18n/el.json | 2 +- i18n/en.json | 2 +- i18n/es.json | 2 +- i18n/et.json | 2 +- i18n/fr.json | 2 +- i18n/he.json | 2 +- i18n/hi.json | 2 +- i18n/hu.json | 2 +- i18n/id.json | 2 +- i18n/it.json | 2 +- i18n/ja.json | 2 +- i18n/ku.json | 2 +- i18n/la.json | 2 +- i18n/lt.json | 2 +- i18n/nl.json | 2 +- i18n/no.json | 2 +- i18n/oc.json | 2 +- i18n/pl.json | 2 +- i18n/pt.json | 2 +- i18n/ru.json | 2 +- i18n/sl.json | 2 +- i18n/sv.json | 2 +- i18n/tr.json | 2 +- i18n/uk.json | 2 +- i18n/zh.json | 2 +- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/i18n/ar.json b/i18n/ar.json index 894b87b..ddca0d6 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/bg.json b/i18n/bg.json index 1385ab1..4a02538 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/ca.json b/i18n/ca.json index d81499c..a6936be 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/cs.json b/i18n/cs.json index 956d1db..34ef19a 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/de.json b/i18n/de.json index 7cecba7..b0991d8 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Verschlüsselte Notiz auf PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besuche diese Verknüpfung um das Dokument zu sehen. Wird die URL an eine andere Person gegeben, so kann diese Person ebenfalls auf dieses Dokument zugreifen.", "URL shortener may expose your decrypt key in URL.": "Der URL-Verkürzer kann den Schlüssel in der URL enthüllen.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/el.json b/i18n/el.json index 54f21a2..6e33978 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/en.json b/i18n/en.json index e38b40e..a96bab5 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/es.json b/i18n/es.json index 307a855..73f6055 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Nota cifrada en PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite este enlace para ver la nota. Dar la URL a cualquier persona también les permite acceder a la nota.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/et.json b/i18n/et.json index 0b4d5cf..b364df2 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/fr.json b/i18n/fr.json index bc53fe1..03d6085 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Message chiffré sur PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visiter ce lien pour voir la note. Donner l'URL à une autre personne lui permet également d'accéder à la note.", "URL shortener may expose your decrypt key in URL.": "Raccourcir l'URL peut exposer votre clé de déchiffrement dans l'URL.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/he.json b/i18n/he.json index eb2d512..046874e 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "הערה מוצפנת ב־PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "נא לבקר בקישור כדי לצפות בהערה. מסירת הקישור לאנשים כלשהם תאפשר גם להם לגשת להערה.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/hi.json b/i18n/hi.json index 4df9ce0..0cd2787 100644 --- a/i18n/hi.json +++ b/i18n/hi.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/hu.json b/i18n/hu.json index a619b3f..1cff1a6 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Titkosított jegyzet a PrivateBinen", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Látogasd meg ezt a hivatkozást a bejegyzés megtekintéséhez. Ha mások számára is megadod ezt a linket, azzal hozzáférnek ők is.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/id.json b/i18n/id.json index 1547ace..1924f81 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Catatan ter-ekrip di PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Kunjungi tautan ini untuk melihat catatan. Memberikan alamat URL pada siapapun juga, akan mengizinkan mereka untuk mengakses catatan, so pasti gitu loh Kaka.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/it.json b/i18n/it.json index 2613579..bda7a1d 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Nota crittografata su PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visita questo collegamento per vedere la nota. Dare l'URL a chiunque consente anche a loro di accedere alla nota.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/ja.json b/i18n/ja.json index ed6971d..807a38c 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/ku.json b/i18n/ku.json index 0e8c299..ae72ebd 100644 --- a/i18n/ku.json +++ b/i18n/ku.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/la.json b/i18n/la.json index 11d062b..2098982 100644 --- a/i18n/la.json +++ b/i18n/la.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/lt.json b/i18n/lt.json index 31f7906..bc287bc 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -185,5 +185,5 @@ "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ą.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/nl.json b/i18n/nl.json index 8e51dfc..65c4e37 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/no.json b/i18n/no.json index e8d9536..62764bf 100644 --- a/i18n/no.json +++ b/i18n/no.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Kryptert notat på PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besøk denne lenken for å se notatet. Hvis lenken deles med andre, vil de også kunne se notatet.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/oc.json b/i18n/oc.json index 1d445b1..a310686 100644 --- a/i18n/oc.json +++ b/i18n/oc.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Nòtas chifradas sus PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visitatz aqueste ligam per veire la nòta. Fornir lo ligam a qualqu’un mai li permet tanben d’accedir a la nòta.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/pl.json b/i18n/pl.json index 8fde350..4b4a67b 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/pt.json b/i18n/pt.json index f83f778..0f6c133 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Nota criptografada no PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite esse link para ver a nota. Dar a URL para qualquer um permite que eles também acessem a nota.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/ru.json b/i18n/ru.json index 03c4945..1c99043 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Зашифрованная запись на PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Посетите эту ссылку чтобы просмотреть запись. Передача ссылки кому либо позволит им получить доступ к записи тоже.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/sl.json b/i18n/sl.json index bac6f98..c4bb0cb 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/sv.json b/i18n/sv.json index a586a4f..f2286e0 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/tr.json b/i18n/tr.json index 1156275..edb89db 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/uk.json b/i18n/uk.json index 3afa64d..1916a24 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -185,5 +185,5 @@ "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.", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/i18n/zh.json b/i18n/zh.json index 65d795e..417dc52 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -185,5 +185,5 @@ "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 缩短可能会暴露您在 URL 中的解密密钥。", - "Download": "Download" + "Save paste": "Save paste" } diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 6575789..1e4eae0 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -213,7 +213,7 @@ endif; - + Date: Sun, 18 Apr 2021 14:14:46 +0200 Subject: [PATCH 036/478] feat: add form-action CSP restriction This follows a suggestion from HTTP Observatory: > Restricts where
contents may be submitted by using form-action 'none', form-action 'self', or specific URIs Fixes #778 --- cfg/conf.sample.php | 2 +- lib/Configuration.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index 570503c..5f8fef1 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -87,7 +87,7 @@ languageselection = false ; async functions and display an error if not and for Chrome to enable ; webassembly support (used for zlib compression). You can remove it if Chrome ; doesn't need to be supported and old browsers don't need to be warned. -; cspheader = "default-src 'none'; base-uri 'self'; 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 'self'; 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 diff --git a/lib/Configuration.php b/lib/Configuration.php index 426cd15..113f6bd 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -55,7 +55,7 @@ class Configuration 'urlshortener' => '', 'qrcode' => true, 'icon' => 'identicon', - 'cspheader' => 'default-src \'none\'; base-uri \'self\'; 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 \'self\'; 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', From 3ca01024fd63512daea82110fa5db4c7379a2f27 Mon Sep 17 00:00:00 2001 From: rugk Date: Sun, 18 Apr 2021 14:16:39 +0200 Subject: [PATCH 037/478] feat: disallow form submission alltogether Following the tests and HTTP Observatory, I think we can disable forms altogether. Fixes https://github.com/PrivateBin/PrivateBin/issues/778 --- cfg/conf.sample.php | 2 +- lib/Configuration.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index 5f8fef1..a7b76a1 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -87,7 +87,7 @@ languageselection = false ; async functions and display an error if not and for Chrome to enable ; webassembly support (used for zlib compression). You can remove it if Chrome ; doesn't need to be supported and old browsers don't need to be warned. -; cspheader = "default-src 'none'; base-uri 'self'; form-action 'self'; 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-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 diff --git a/lib/Configuration.php b/lib/Configuration.php index 113f6bd..a99c3f9 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -55,7 +55,7 @@ class Configuration 'urlshortener' => '', 'qrcode' => true, 'icon' => 'identicon', - 'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'self\'; 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-popups allow-modals allow-downloads', 'zerobincompatibility' => false, 'httpwarning' => true, 'compression' => 'zlib', From 4b2f2920a2b2dd08af58d38fd0e58b97c3956b86 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:03:57 +0200 Subject: [PATCH 038/478] New translations en.json (Indonesian) --- i18n/id.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/id.json b/i18n/id.json index a7c01b3..b28af88 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -184,5 +184,6 @@ "Close": "Tutup", "Encrypted note on PrivateBin": "Catatan ter-ekrip di PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Kunjungi tautan ini untuk melihat catatan. Memberikan alamat URL pada siapapun juga, akan mengizinkan mereka untuk mengakses catatan, so pasti gitu loh Kaka.", - "URL shortener may expose your decrypt key in URL.": "Pemendek URL mungkin akan menampakkan kunci dekrip Anda dalam URL." + "URL shortener may expose your decrypt key in URL.": "Pemendek URL mungkin akan menampakkan kunci dekrip Anda dalam URL.", + "Save paste": "Save paste" } From dac5bd1d93fa1c969b3ae24d40ef9bd6b7b59cb7 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:03:59 +0200 Subject: [PATCH 039/478] New translations en.json (Dutch) --- i18n/nl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/nl.json b/i18n/nl.json index 54586ff..65c4e37 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From db402baa14e125d06c160341affbd477d088b920 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:00 +0200 Subject: [PATCH 040/478] New translations en.json (Occitan) --- i18n/oc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/oc.json b/i18n/oc.json index 49e3dde..a310686 100644 --- a/i18n/oc.json +++ b/i18n/oc.json @@ -184,5 +184,6 @@ "Close": "Tampar", "Encrypted note on PrivateBin": "Nòtas chifradas sus PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visitatz aqueste ligam per veire la nòta. Fornir lo ligam a qualqu’un mai li permet tanben d’accedir a la nòta.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 478f806e9c897bd50a995f34a050e02e33bfd90a Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:01 +0200 Subject: [PATCH 041/478] New translations en.json (Latin) --- i18n/la.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/la.json b/i18n/la.json index 4d648cb..2098982 100644 --- a/i18n/la.json +++ b/i18n/la.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From bd83415c825a8c7ead351c6fd27736666ee87c5c Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:02 +0200 Subject: [PATCH 042/478] New translations en.json (Hindi) --- i18n/hi.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/hi.json b/i18n/hi.json index a248e82..0cd2787 100644 --- a/i18n/hi.json +++ b/i18n/hi.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From c0207d00a2f7b6b42197e5eef32eab621cf7d32c Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:04 +0200 Subject: [PATCH 043/478] New translations en.json (Chinese Simplified) --- i18n/zh.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/zh.json b/i18n/zh.json index 2e1dc12..417dc52 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -184,5 +184,6 @@ "Close": "关闭", "Encrypted note on PrivateBin": "PrivateBin上的加密笔记", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "访问这个链接来查看该笔记。 将这个URL发送给任何人即可允许其访问该笔记。", - "URL shortener may expose your decrypt key in URL.": "URL 缩短可能会暴露您在 URL 中的解密密钥。" + "URL shortener may expose your decrypt key in URL.": "URL 缩短可能会暴露您在 URL 中的解密密钥。", + "Save paste": "Save paste" } From dd4633ff8f8422e8eda9aee121b018a144127ea7 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:05 +0200 Subject: [PATCH 044/478] New translations en.json (Ukrainian) --- i18n/uk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/uk.json b/i18n/uk.json index 2808c71..1916a24 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 9bd04c55c94b090fb01087fbc5f0a0e459ec96e9 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:06 +0200 Subject: [PATCH 045/478] New translations en.json (Turkish) --- i18n/tr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/tr.json b/i18n/tr.json index daedf8f..edb89db 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 45b3ec4ac6207ff6bccb08664c0f8a8e0cd66ba2 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:07 +0200 Subject: [PATCH 046/478] New translations en.json (Swedish) --- i18n/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/sv.json b/i18n/sv.json index e085949..f2286e0 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 56d993ca8297bd678087a2adc7ceb22a399aa7f9 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:08 +0200 Subject: [PATCH 047/478] New translations en.json (Slovenian) --- i18n/sl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/sl.json b/i18n/sl.json index 51b9caa..c4bb0cb 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From c7a86ebd5cb45a13207776a97e117d180680fcf6 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:09 +0200 Subject: [PATCH 048/478] New translations en.json (Russian) --- i18n/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/ru.json b/i18n/ru.json index b697f70..1c99043 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -184,5 +184,6 @@ "Close": "Закрыть", "Encrypted note on PrivateBin": "Зашифрованная запись на PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Посетите эту ссылку чтобы просмотреть запись. Передача ссылки кому либо позволит им получить доступ к записи тоже.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 39867d81513f0de129e894b98b5307b03b28941a Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:10 +0200 Subject: [PATCH 049/478] New translations en.json (Portuguese) --- i18n/pt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/pt.json b/i18n/pt.json index 6049207..0f6c133 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -184,5 +184,6 @@ "Close": "Fechar", "Encrypted note on PrivateBin": "Nota criptografada no PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite esse link para ver a nota. Dar a URL para qualquer um permite que eles também acessem a nota.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 3e4def20690d45b30b226e29c8825fc0d4af80b1 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:11 +0200 Subject: [PATCH 050/478] New translations en.json (Polish) --- i18n/pl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/pl.json b/i18n/pl.json index c18ad22..4b4a67b 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 0887f567ab21380e7758c69298114fe6b85d233c Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:12 +0200 Subject: [PATCH 051/478] New translations en.json (Norwegian) --- i18n/no.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/no.json b/i18n/no.json index ac6e369..62764bf 100644 --- a/i18n/no.json +++ b/i18n/no.json @@ -184,5 +184,6 @@ "Close": "Steng", "Encrypted note on PrivateBin": "Kryptert notat på PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besøk denne lenken for å se notatet. Hvis lenken deles med andre, vil de også kunne se notatet.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 48916d5df7f6cb809f6ba59f6e83bb8393601b8d Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:13 +0200 Subject: [PATCH 052/478] New translations en.json (Lithuanian) --- i18n/lt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/lt.json b/i18n/lt.json index 7ef45bf..bc287bc 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -184,5 +184,6 @@ "Close": "Užverti", "Encrypted note on PrivateBin": "Šifruoti užrašai ties PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Norėdami matyti užrašus, aplankykite šį tinklalapį. Pasidalinus šiuo URL adresu su kitais žmonėmis, jiems taip pat bus leidžiama prieiga prie šių užrašų.", - "URL shortener may expose your decrypt key in URL.": "URL trumpinimo įrankis gali atskleisti URL adrese jūsų iššifravimo raktą." + "URL shortener may expose your decrypt key in URL.": "URL trumpinimo įrankis gali atskleisti URL adrese jūsų iššifravimo raktą.", + "Save paste": "Save paste" } From 2bc7e8e38f5feb75948e53066ef5f48bb2e9f51b Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:14 +0200 Subject: [PATCH 053/478] New translations en.json (Catalan) --- i18n/ca.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/ca.json b/i18n/ca.json index a4e6d3f..a6936be 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 6b5e7c1b49b951f527f20f72a9a9811fafa41048 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:15 +0200 Subject: [PATCH 054/478] New translations en.json (Kurdish) --- i18n/ku.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/ku.json b/i18n/ku.json index 0ff29ac..ae72ebd 100644 --- a/i18n/ku.json +++ b/i18n/ku.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 492cdc99269ed47233a3b6ee2b49c6e7946b14b3 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:17 +0200 Subject: [PATCH 055/478] New translations en.json (Japanese) --- i18n/ja.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/ja.json b/i18n/ja.json index bf4f7ca..807a38c 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 926fab30e9ebd45682576e4c94ec188e65737c44 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:18 +0200 Subject: [PATCH 056/478] New translations en.json (Italian) --- i18n/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/it.json b/i18n/it.json index 81b11a8..bda7a1d 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -184,5 +184,6 @@ "Close": "Chiudi", "Encrypted note on PrivateBin": "Nota crittografata su PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visita questo collegamento per vedere la nota. Dare l'URL a chiunque consente anche a loro di accedere alla nota.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 4514f1f3a459573a21cce0a01ee7f8360febd3ce Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:19 +0200 Subject: [PATCH 057/478] New translations en.json (Hungarian) --- i18n/hu.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/hu.json b/i18n/hu.json index b870836..1cff1a6 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -184,5 +184,6 @@ "Close": "Bezárás", "Encrypted note on PrivateBin": "Titkosított jegyzet a PrivateBinen", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Látogasd meg ezt a hivatkozást a bejegyzés megtekintéséhez. Ha mások számára is megadod ezt a linket, azzal hozzáférnek ők is.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From db0db4ebffddb188a657a2060f089916a6d97a2f Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:20 +0200 Subject: [PATCH 058/478] New translations en.json (Hebrew) --- i18n/he.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/he.json b/i18n/he.json index 206587b..046874e 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -184,5 +184,6 @@ "Close": "סגירה", "Encrypted note on PrivateBin": "הערה מוצפנת ב־PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "נא לבקר בקישור כדי לצפות בהערה. מסירת הקישור לאנשים כלשהם תאפשר גם להם לגשת להערה.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 67fd327df425d8b79a66aba66d111062e5d1e141 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:21 +0200 Subject: [PATCH 059/478] New translations en.json (Greek) --- i18n/el.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/el.json b/i18n/el.json index 6fd45e4..6e33978 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 982a4f957c0bde89fe1ea6d2f4360859821dfe9f Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:22 +0200 Subject: [PATCH 060/478] New translations en.json (German) --- i18n/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/de.json b/i18n/de.json index dd8a48a..b0991d8 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -184,5 +184,6 @@ "Close": "Schliessen", "Encrypted note on PrivateBin": "Verschlüsselte Notiz auf PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besuche diese Verknüpfung um das Dokument zu sehen. Wird die URL an eine andere Person gegeben, so kann diese Person ebenfalls auf dieses Dokument zugreifen.", - "URL shortener may expose your decrypt key in URL.": "Der URL-Verkürzer kann den Schlüssel in der URL enthüllen." + "URL shortener may expose your decrypt key in URL.": "Der URL-Verkürzer kann den Schlüssel in der URL enthüllen.", + "Save paste": "Save paste" } From 63d20330b4ea28a3dae42d805ffabfd482c18c21 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:23 +0200 Subject: [PATCH 061/478] New translations en.json (Czech) --- i18n/cs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/cs.json b/i18n/cs.json index 1a6249a..34ef19a 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 4a73afa057441e6e74c2d5e4fbb9452a56e71302 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:24 +0200 Subject: [PATCH 062/478] New translations en.json (Bulgarian) --- i18n/bg.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/bg.json b/i18n/bg.json index 8ddd1f5..4a02538 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From cd1b0e0a50607d2239b5e6113cd254f3694027a8 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:25 +0200 Subject: [PATCH 063/478] New translations en.json (Arabic) --- i18n/ar.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/ar.json b/i18n/ar.json index 8b8e1ae..ddca0d6 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -184,5 +184,6 @@ "Close": "Close", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 14ff704b28c6619e5a1b66a5f17085f81aeaa74d Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:26 +0200 Subject: [PATCH 064/478] New translations en.json (Spanish) --- i18n/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/es.json b/i18n/es.json index 4e93e66..73f6055 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -184,5 +184,6 @@ "Close": "Cerrar", "Encrypted note on PrivateBin": "Nota cifrada en PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite este enlace para ver la nota. Dar la URL a cualquier persona también les permite acceder a la nota.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL." + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" } From 30228cc33c68d985fc1a7cab6a4476abfad814f9 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:27 +0200 Subject: [PATCH 065/478] New translations en.json (French) --- i18n/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/fr.json b/i18n/fr.json index 409d449..03d6085 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -184,5 +184,6 @@ "Close": "Fermer", "Encrypted note on PrivateBin": "Message chiffré sur PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visiter ce lien pour voir la note. Donner l'URL à une autre personne lui permet également d'accéder à la note.", - "URL shortener may expose your decrypt key in URL.": "Raccourcir l'URL peut exposer votre clé de déchiffrement dans l'URL." + "URL shortener may expose your decrypt key in URL.": "Raccourcir l'URL peut exposer votre clé de déchiffrement dans l'URL.", + "Save paste": "Save paste" } From 993abd746e0b774b868e22ce3452b2a5c9e52403 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:04:28 +0200 Subject: [PATCH 066/478] New translations en.json (Estonian) --- i18n/et.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/et.json b/i18n/et.json index 877c732..b364df2 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -184,5 +184,6 @@ "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." + "URL shortener may expose your decrypt key in URL.": "URL-i lühendaja võib paljastada sinu dekrüpteerimisvõtme URL-is.", + "Save paste": "Save paste" } From fcb6422663954797d7344f045c30a45002b0d98a Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 18 Apr 2021 21:05:32 +0200 Subject: [PATCH 067/478] re-adding CSP directive sandbox allow-forms, it is needed for the password input form to work on the JS side --- cfg/conf.sample.php | 2 +- lib/Configuration.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index a7b76a1..ceddad6 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -87,7 +87,7 @@ languageselection = false ; async functions and display an error if not and for Chrome to enable ; webassembly support (used for zlib compression). You can remove it if Chrome ; doesn't need to be supported and old browsers don't need to be warned. -; cspheader = "default-src 'none'; 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-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 diff --git a/lib/Configuration.php b/lib/Configuration.php index a99c3f9..e509d98 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -55,7 +55,7 @@ class Configuration 'urlshortener' => '', 'qrcode' => true, 'icon' => 'identicon', - '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-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', From 010f9db274f53e9ea4ed618128223abfac4c78ed Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 22:00:35 +0200 Subject: [PATCH 068/478] New translations en.json (French) --- i18n/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/fr.json b/i18n/fr.json index 03d6085..0e14d79 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Message chiffré sur PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visiter ce lien pour voir la note. Donner l'URL à une autre personne lui permet également d'accéder à la note.", "URL shortener may expose your decrypt key in URL.": "Raccourcir l'URL peut exposer votre clé de déchiffrement dans l'URL.", - "Save paste": "Save paste" + "Save paste": "Sauver le paste" } From 53e23b7422d08d9e5755fa6c83e995eaf639b866 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 22:00:36 +0200 Subject: [PATCH 069/478] New translations en.json (Spanish) --- i18n/es.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/es.json b/i18n/es.json index 73f6055..5aa2b38 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -184,6 +184,6 @@ "Close": "Cerrar", "Encrypted note on PrivateBin": "Nota cifrada en PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite este enlace para ver la nota. Dar la URL a cualquier persona también les permite acceder a la nota.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Save paste": "Save paste" + "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\"" } From d0c6ab224fb91ae2a65609b65b1f2e0c888e978b Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 22:00:37 +0200 Subject: [PATCH 070/478] New translations en.json (German) --- i18n/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/de.json b/i18n/de.json index b0991d8..7bc38d1 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -185,5 +185,5 @@ "Encrypted note on PrivateBin": "Verschlüsselte Notiz auf PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besuche diese Verknüpfung um das Dokument zu sehen. Wird die URL an eine andere Person gegeben, so kann diese Person ebenfalls auf dieses Dokument zugreifen.", "URL shortener may expose your decrypt key in URL.": "Der URL-Verkürzer kann den Schlüssel in der URL enthüllen.", - "Save paste": "Save paste" + "Save paste": "Text speichern" } From 1b8351fef9d744b42bdf64d88f2436028adf18ff Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 18 Apr 2021 22:00:38 +0200 Subject: [PATCH 071/478] New translations en.json (Italian) --- i18n/it.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/it.json b/i18n/it.json index bda7a1d..659816e 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -184,6 +184,6 @@ "Close": "Chiudi", "Encrypted note on PrivateBin": "Nota crittografata su PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visita questo collegamento per vedere la nota. Dare l'URL a chiunque consente anche a loro di accedere alla nota.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Save paste": "Save paste" + "URL shortener may expose your decrypt key in URL.": "URL shortener può esporre la tua chiave decrittografata nell'URL.", + "Save paste": "Salva il messagio" } From b47b8cf05088fe28e3424d7555219e9fe7e5481a Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Tue, 20 Apr 2021 12:05:08 +0200 Subject: [PATCH 072/478] New translations en.json (Russian) --- i18n/ru.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/ru.json b/i18n/ru.json index 1c99043..b6f9586 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -184,6 +184,6 @@ "Close": "Закрыть", "Encrypted note on PrivateBin": "Зашифрованная запись на PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Посетите эту ссылку чтобы просмотреть запись. Передача ссылки кому либо позволит им получить доступ к записи тоже.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Save paste": "Save paste" + "URL shortener may expose your decrypt key in URL.": "Сервис сокращения ссылок может получить ваш ключ расшифровки из ссылки.", + "Save paste": "Сохранить запись" } From 4c329be95f808321b99867c2d22f410ad22a860c Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Tue, 20 Apr 2021 18:54:09 +0200 Subject: [PATCH 073/478] New translations en.json (Norwegian) --- i18n/no.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/no.json b/i18n/no.json index 62764bf..b0ca95a 100644 --- a/i18n/no.json +++ b/i18n/no.json @@ -184,6 +184,6 @@ "Close": "Steng", "Encrypted note on PrivateBin": "Kryptert notat på PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besøk denne lenken for å se notatet. Hvis lenken deles med andre, vil de også kunne se notatet.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Save paste": "Save paste" + "URL shortener may expose your decrypt key in URL.": "URL forkorter kan avsløre dekrypteringsnøkkelen.", + "Save paste": "Lagre utklipp" } From 4d3a2ae946c77ead9820e43bce6870a4c587a065 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 22 Apr 2021 03:46:58 +0200 Subject: [PATCH 074/478] New translations en.json (Chinese Simplified) --- i18n/zh.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/zh.json b/i18n/zh.json index 417dc52..4156e54 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -185,5 +185,5 @@ "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 缩短可能会暴露您在 URL 中的解密密钥。", - "Save paste": "Save paste" + "Save paste": "保存内容" } From 0a9cd0545365e67101b2ae245e03716032f2ee02 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Wed, 28 Apr 2021 13:52:52 +0200 Subject: [PATCH 075/478] New translations en.json (Estonian) --- i18n/et.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/et.json b/i18n/et.json index b364df2..9ab2840 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -185,5 +185,5 @@ "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": "Save paste" + "Save paste": "Salvesta kleebe" } From 17c1284ccfc104692d4b9a4e62fa1ee57cf32f7f Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 3 May 2021 16:32:06 +0200 Subject: [PATCH 076/478] New translations en.json (Chinese Simplified) --- i18n/zh.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i18n/zh.json b/i18n/zh.json index 4156e54..06428d8 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -1,12 +1,12 @@ { "PrivateBin": "PrivateBin", - "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s是一个极简、开源、对粘贴内容毫不知情的在线粘贴板,数据%s在浏览器内%s进行AES-256加密。", + "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s是一个极简、开源、对粘贴内容毫不知情的在线粘贴板,数据%s在浏览器内%s进行AES-256加密和解密。", "More information on the 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 秒只能粘贴一次。", From e6def62581e8a04e7c255b709c5b05d49f52b53e Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 3 May 2021 17:39:47 +0200 Subject: [PATCH 077/478] New translations en.json (Chinese Simplified) --- i18n/zh.json | 58 ++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/i18n/zh.json b/i18n/zh.json index 06428d8..2ab63bf 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -1,29 +1,29 @@ { "PrivateBin": "PrivateBin", - "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s是一个极简、开源、对粘贴内容毫不知情的在线粘贴板,数据%s在浏览器内%s进行AES-256加密和解密。", + "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s 是一个极简、开源、对粘贴内容毫不知情的在线粘贴板,数据%s在浏览器内%s进行 AES-256 加密和解密。", "More information on the 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] 部分。", + "%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": "复制", @@ -112,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生成的头像", @@ -126,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中缺失解密密钥(是否使用了重定向或者短链接导致密钥丢失?)", @@ -144,46 +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发送给任何人即可允许其访问该笔记。", - "URL shortener may expose your decrypt key in URL.": "URL 缩短可能会暴露您在 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": "保存内容" } From 377d7d565bad82e2d987c61beec59dd42ef965b2 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Tue, 4 May 2021 00:29:57 +0200 Subject: [PATCH 078/478] New translations en.json (Indonesian) --- i18n/id.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/id.json b/i18n/id.json index b28af88..ea2c80a 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -185,5 +185,5 @@ "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": "Save paste" + "Save paste": "Simpan paste" } From 3f92d4c0388ae49bfb95c24727f08dedc7d6e2cf Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Tue, 4 May 2021 01:30:38 +0200 Subject: [PATCH 079/478] New translations en.json (Indonesian) --- i18n/id.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/i18n/id.json b/i18n/id.json index ea2c80a..c580ca6 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -9,9 +9,9 @@ "%s requires configuration section [%s] to be present in configuration file.": "%s membutuhkan bagian konfigurasi [%s] untuk ada di file konfigurasi.", "Please wait %d seconds between each post.": [ "Silahkan menunggu %d detik antara masing-masing postingan. (tunggal)", - "Silahkan menunggu %d detik antara masing-masing postingan. (jamak ke-1)", - "Silahkan menunggu %d detik antara masing-masing postingan. (jamak ke-2)", - "Silahkan menunggu %d detik antara masing-masing postingan. (jamak ke-3)" + "Silahkan menunggu %d detik antara masing-masing postingan.", + "Silahkan menunggu %d detik antara masing-masing postingan.", + "Silahkan menunggu %d detik antara masing-masing postingan." ], "Paste is limited to %s of encrypted data.": "Paste dibatasi sampai %s dari data yang dienskripsi.", "Invalid data.": "Data tidak valid.", @@ -36,27 +36,27 @@ "Toggle navigation": "Alihkan navigasi", "%d seconds": [ "%d detik (tunggal)", - "%d detik (jamak ke-1)", - "%d detik (jamak ke-2)", - "%d detik (jamak ke-3)" + "%d detik", + "%d detik", + "%d detik" ], "%d minutes": [ "%d menit (tunggal)", - "%d menit (jamak ke-1)", - "%d menit (jamak ke-2)", - "%d menit (jamak ke-3)" + "%d menit", + "%d menit", + "%d menit" ], "%d hours": [ "%d jam (tunggal)", - "%d jam (jamak ke-1)", - "%d jam (jamak ke-2)", - "%d jam (jamak ke-3)" + "%d jam", + "%d jam", + "%d jam" ], "%d days": [ "%d hari (tunggal)", - "%d hari (jamak ke-1)", - "%d hari (jamak ke-2)", - "%d hari (jamak ke-3)" + "%d hari", + "%d hari", + "%d hari" ], "%d weeks": [ "%d minggu (tunggal)", From 7d82c82fd98b4496dac88154b5ed034f5b5276db Mon Sep 17 00:00:00 2001 From: LinQhost Managed hosting Date: Tue, 4 May 2021 10:29:25 +0200 Subject: [PATCH 080/478] Make it possible to exempt ips from the rate-limiter --- cfg/conf.sample.php | 4 ++ composer.json | 5 ++- composer.lock | 70 +++++++++++++++++++++++++++++- lib/Configuration.php | 1 + lib/Persistence/TrafficLimiter.php | 51 ++++++++++++++++++++++ 5 files changed, 128 insertions(+), 3 deletions(-) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index e958c88..0837645 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -135,6 +135,10 @@ 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" diff --git a/composer.json b/composer.json index 899c863..ff79af9 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "require" : { "php" : "^5.6.0 || ^7.0 || ^8.0", "paragonie/random_compat" : "2.0.19", - "yzalis/identicon" : "2.0.0" + "yzalis/identicon" : "2.0.0", + "mlocati/ip-lib": "^1.14" }, "require-dev" : { "phpunit/phpunit" : "^4.6 || ^5.0" @@ -39,4 +40,4 @@ "config" : { "autoloader-suffix" : "DontChange" } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 80d2276..66a9c8a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,76 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9d110873bf15a6abd66734e8a818134c", + "content-hash": "8138b0e4bb3fcaab9ca4c02bbe35d891", "packages": [ + { + "name": "mlocati/ip-lib", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/mlocati/ip-lib.git", + "reference": "882bc0e115970a536b13bcfa59f312783fce08c8" + }, + "dist": { + "type": "zip", + "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.19", diff --git a/lib/Configuration.php b/lib/Configuration.php index 2a326ca..133059d 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -82,6 +82,7 @@ class Configuration 'limit' => 10, 'header' => null, 'dir' => 'data', + 'exemptedIp' => null, ), 'purge' => array( 'limit' => 300, diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index a16cd0b..31f107f 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])) { @@ -99,6 +124,32 @@ class TrafficLimiter extends AbstractPersistence if (self::$_limit < 1) { return true; } + + // Check if $_ipKey is exempted from ratelimiting + if (!is_null(self::$_exemptedIp)) { + $exIp_array = explode(",", self::$_exemptedIp); + foreach ($exIp_array as $ipRange) { + // Match $_ipKey to $ipRange and if it matches it will return with a true + $address = \IPLib\Factory::addressFromString($_SERVER[self::$_ipKey]); + $range = \IPLib\Factory::rangeFromString(trim($ipRange)); + // If $range is null something went wrong (possible invalid ip given in config) + if ($range == null) { + $contained = false; + } else { + // Ip-lib does throws and exception when something goes wrong, if so we want to catch it and set contained to false + try { + $contained = $address->matches($range); + } catch (Exception $e) { + // If something is wrong with matching the ip, we set $contained to false + $contained = false; + } + } + // Matches return true! + if ($contained == true) { + return true; + } + } + } $file = 'traffic_limiter.php'; if (self::_exists($file)) { From b21efd8336c12f39bcf07e5ba9d0387ceee9955d Mon Sep 17 00:00:00 2001 From: rodehoed Date: Tue, 4 May 2021 11:01:46 +0200 Subject: [PATCH 081/478] Code quality --- lib/Persistence/TrafficLimiter.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 31f107f..46eddc4 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -62,11 +62,11 @@ class TrafficLimiter extends AbstractPersistence } /** - * set a list of ip(ranges) as array + * set a list of ip(ranges) as string * * @access public * @static - * @param array $exemptedIps + * @param string $exemptedIps */ public static function setExemptedIp($exemptedIp) { @@ -139,7 +139,7 @@ class TrafficLimiter extends AbstractPersistence // Ip-lib does throws and exception when something goes wrong, if so we want to catch it and set contained to false try { $contained = $address->matches($range); - } catch (Exception $e) { + } catch (\Exception $e) { // If something is wrong with matching the ip, we set $contained to false $contained = false; } From 805eb288d92be1716214156af50b69dbf88a0afd Mon Sep 17 00:00:00 2001 From: rodehoed Date: Tue, 4 May 2021 11:14:11 +0200 Subject: [PATCH 082/478] QA --- lib/Persistence/TrafficLimiter.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 46eddc4..06916c5 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -82,7 +82,6 @@ class TrafficLimiter extends AbstractPersistence */ public static function setConfiguration(Configuration $conf) { - self::setLimit($conf->getKey('limit', 'traffic')); self::setPath($conf->getKey('dir', 'traffic')); self::setExemptedIp($conf->getKey('exemptedIp', 'traffic')); @@ -127,11 +126,11 @@ class TrafficLimiter extends AbstractPersistence // Check if $_ipKey is exempted from ratelimiting if (!is_null(self::$_exemptedIp)) { - $exIp_array = explode(",", self::$_exemptedIp); + $exIp_array = explode(',', self::$_exemptedIp); foreach ($exIp_array as $ipRange) { // Match $_ipKey to $ipRange and if it matches it will return with a true $address = \IPLib\Factory::addressFromString($_SERVER[self::$_ipKey]); - $range = \IPLib\Factory::rangeFromString(trim($ipRange)); + $range = \IPLib\Factory::rangeFromString(trim($ipRange)); // If $range is null something went wrong (possible invalid ip given in config) if ($range == null) { $contained = false; From c3ad4a4b4d48946e89785171b83093f3527d5cf5 Mon Sep 17 00:00:00 2001 From: rodehoed Date: Tue, 4 May 2021 11:18:06 +0200 Subject: [PATCH 083/478] QA --- lib/Persistence/TrafficLimiter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 06916c5..005fa5f 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -30,7 +30,7 @@ class TrafficLimiter extends AbstractPersistence * @var int */ private static $_limit = 10; - + /** * listed ips are exempted from limits, defaults to null * From 4296b43832a59862e7d4abf56e48475ead37c80e Mon Sep 17 00:00:00 2001 From: rodehoed Date: Tue, 4 May 2021 11:19:34 +0200 Subject: [PATCH 084/478] QA --- lib/Persistence/TrafficLimiter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 005fa5f..ba0ac33 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -123,7 +123,7 @@ class TrafficLimiter extends AbstractPersistence if (self::$_limit < 1) { return true; } - + // Check if $_ipKey is exempted from ratelimiting if (!is_null(self::$_exemptedIp)) { $exIp_array = explode(',', self::$_exemptedIp); From a806a6455eb4591e55ab6e6e90a4d64aa729f95d Mon Sep 17 00:00:00 2001 From: rodehoed Date: Tue, 4 May 2021 11:20:24 +0200 Subject: [PATCH 085/478] QA --- lib/Configuration.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Configuration.php b/lib/Configuration.php index 133059d..da62a1d 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -79,9 +79,9 @@ class Configuration 'markdown' => 'Markdown', ), 'traffic' => array( - 'limit' => 10, - 'header' => null, - 'dir' => 'data', + 'limit' => 10, + 'header' => null, + 'dir' => 'data', 'exemptedIp' => null, ), 'purge' => array( From 12aa325494fb3cca04c4ece52b317ab043036171 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Tue, 4 May 2021 13:39:44 +0200 Subject: [PATCH 086/478] New translations en.json (Russian) --- i18n/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/ru.json b/i18n/ru.json index b6f9586..a911233 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -77,7 +77,7 @@ "%d лет" ], "Never": "Никогда", - "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Примечание: Этот сервис тестовый: Данные могут быть удалены в любое время. Котята умрут, если вы будете злоупотреблять серсисом.", + "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Примечание: Этот сервис тестовый: Данные могут быть удалены в любое время. Котята умрут, если вы будете злоупотреблять сервисом.", "This document will expire in %d seconds.": [ "Документ будет удален через %d секунду.", "Документ будет удален через %d секунды.", From 2b0ebdb6c70b68c2127129dc45aebeac7083d0df Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Tue, 4 May 2021 19:43:08 +0200 Subject: [PATCH 087/478] New translations en.json (Turkish) --- i18n/tr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i18n/tr.json b/i18n/tr.json index edb89db..a543575 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -14,8 +14,8 @@ "Please wait %d seconds between each post. (3rd plural)" ], "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", - "Invalid data.": "Invalid data.", - "You are unlucky. Try again.": "You are unlucky. Try again.", + "Invalid data.": "Geçersiz veri.", + "You are unlucky. Try again.": "Lütfen tekrar deneyiniz.", "Error saving comment. Sorry.": "Error saving comment. Sorry.", "Error saving paste. Sorry.": "Error saving paste. Sorry.", "Invalid paste ID.": "Invalid paste ID.", @@ -24,8 +24,8 @@ "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", + "New": "Yeni", + "Send": "Gönder", "Clone": "Clone", "Raw text": "Raw text", "Expires": "Expires", From e572e9c79c5258d7167fdfa797619f2831575ba2 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Tue, 4 May 2021 20:48:34 +0200 Subject: [PATCH 088/478] New translations en.json (Turkish) --- i18n/tr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i18n/tr.json b/i18n/tr.json index a543575..5f3c6c0 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -1,8 +1,8 @@ { "PrivateBin": "PrivateBin", "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", - "More information on the project page.": "More information on the project page.", - "Because ignorance is bliss": "Because ignorance is bliss", + "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.", @@ -59,8 +59,8 @@ "%d days (3rd plural)" ], "%d weeks": [ - "%d week (singular)", - "%d weeks (1st plural)", + "%d hafta (tekil)", + "%d haftalar (çoğul)", "%d weeks (2nd plural)", "%d weeks (3rd plural)" ], From 89bdc92451184420bea4cc6b42282f2875676c93 Mon Sep 17 00:00:00 2001 From: Rodehoed Date: Thu, 6 May 2021 12:13:03 +0200 Subject: [PATCH 089/478] Put the ip-matching function in a private function --- lib/Persistence/TrafficLimiter.php | 54 ++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index ba0ac33..cbe6adf 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -107,6 +107,39 @@ class TrafficLimiter extends AbstractPersistence return hash_hmac($algo, $_SERVER[self::$_ipKey], ServerSalt::get()); } + /** + * Validate $_ipKey against configured ipranges. If matched ratelimiter will ignore ip + * + * @access private + * @static + * @param string $algo + * @return string + */ + private static function matchIp($ipRange = null) + { + // Match $_ipKey to $ipRange and if it matches it will return with a true + $address = \IPLib\Factory::addressFromString($_SERVER[self::$_ipKey]); + $range = \IPLib\Factory::rangeFromString(trim($ipRange)); + // If $range is null something went wrong (possible invalid ip given in config) + if ($range == null) { + return false; + } else { + // Ip-lib does throws and exception when something goes wrong, if so we want to catch it and set contained to false + try { + $contained = $address->matches($range); + } catch (\Exception $e) { + // If something is wrong with matching the ip, we set $contained to false + return false; + } + } + // Matches return true! + if ($contained === true) { + return true; + } else { + return false; + } + } + /** * traffic limiter * @@ -123,28 +156,13 @@ class TrafficLimiter extends AbstractPersistence if (self::$_limit < 1) { return true; } - + error_reporting(-1); // Check if $_ipKey is exempted from ratelimiting if (!is_null(self::$_exemptedIp)) { $exIp_array = explode(',', self::$_exemptedIp); foreach ($exIp_array as $ipRange) { - // Match $_ipKey to $ipRange and if it matches it will return with a true - $address = \IPLib\Factory::addressFromString($_SERVER[self::$_ipKey]); - $range = \IPLib\Factory::rangeFromString(trim($ipRange)); - // If $range is null something went wrong (possible invalid ip given in config) - if ($range == null) { - $contained = false; - } else { - // Ip-lib does throws and exception when something goes wrong, if so we want to catch it and set contained to false - try { - $contained = $address->matches($range); - } catch (\Exception $e) { - // If something is wrong with matching the ip, we set $contained to false - $contained = false; - } - } - // Matches return true! - if ($contained == true) { + if (self::matchIp($ipRange) === true) + { return true; } } From 502bb5fa15b12a232c720bf787ef45f898196934 Mon Sep 17 00:00:00 2001 From: Rodehoed Date: Thu, 6 May 2021 12:18:44 +0200 Subject: [PATCH 090/478] Put the ip-matching function in a private function --- lib/Persistence/TrafficLimiter.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index cbe6adf..3975540 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -112,8 +112,8 @@ class TrafficLimiter extends AbstractPersistence * * @access private * @static - * @param string $algo - * @return string + * @param string $ipRange + * @return bool */ private static function matchIp($ipRange = null) { @@ -156,13 +156,12 @@ class TrafficLimiter extends AbstractPersistence if (self::$_limit < 1) { return true; } - error_reporting(-1); + // 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) - { + if (self::matchIp($ipRange) === true) { return true; } } From f339c0b1c0120ae0c3c22c450fb8a753db5f6317 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 9 May 2021 14:11:14 +0200 Subject: [PATCH 091/478] New translations en.json (Indonesian) --- i18n/id.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/i18n/id.json b/i18n/id.json index c580ca6..e58900a 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -8,7 +8,7 @@ "%s requires php %s or above to work. Sorry.": "%s memerlukan php %s atau versi diatasnya untuk dapat dijalankan. Maaf.", "%s requires configuration section [%s] to be present in configuration file.": "%s membutuhkan bagian konfigurasi [%s] untuk ada di file konfigurasi.", "Please wait %d seconds between each post.": [ - "Silahkan menunggu %d detik antara masing-masing postingan. (tunggal)", + "Silahkan menunggu %d detik antara masing-masing postingan.", "Silahkan menunggu %d detik antara masing-masing postingan.", "Silahkan menunggu %d detik antara masing-masing postingan.", "Silahkan menunggu %d detik antara masing-masing postingan." @@ -35,31 +35,31 @@ "Discussion": "Diskusi", "Toggle navigation": "Alihkan navigasi", "%d seconds": [ - "%d detik (tunggal)", + "%d detik", "%d detik", "%d detik", "%d detik" ], "%d minutes": [ - "%d menit (tunggal)", + "%d menit", "%d menit", "%d menit", "%d menit" ], "%d hours": [ - "%d jam (tunggal)", + "%d jam", "%d jam", "%d jam", "%d jam" ], "%d days": [ - "%d hari (tunggal)", + "%d hari", "%d hari", "%d hari", "%d hari" ], "%d weeks": [ - "%d minggu (tunggal)", + "%d minggu", "%d minggu", "%d minggu", "%d minggu" From 5812a6bb68189415866f4ab4728d2497de7d5795 Mon Sep 17 00:00:00 2001 From: rodehoed <6515395+rodehoed@users.noreply.github.com> Date: Wed, 19 May 2021 08:47:35 +0200 Subject: [PATCH 092/478] Optimized the canPass() functions --- lib/Persistence/TrafficLimiter.php | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 3975540..34584b5 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -120,24 +120,18 @@ class TrafficLimiter extends AbstractPersistence // Match $_ipKey to $ipRange and if it matches it will return with a true $address = \IPLib\Factory::addressFromString($_SERVER[self::$_ipKey]); $range = \IPLib\Factory::rangeFromString(trim($ipRange)); - // If $range is null something went wrong (possible invalid ip given in config) - if ($range == null) { - return false; - } else { - // Ip-lib does throws and exception when something goes wrong, if so we want to catch it and set contained to false - try { - $contained = $address->matches($range); - } catch (\Exception $e) { - // If something is wrong with matching the ip, we set $contained to false - return false; - } - } - // Matches return true! - if ($contained === true) { - return true; - } else { - return false; + + // If $range is null something went wrong (possible invalid ip given in config). It's here becaue matches($range) does not accepts null vallue + if ($range == null) return false; + + // Ip-lib does throws and exception when something goes wrong, if so we want to catch it and set contained to false + try { + return $address->matches($range); + } catch (\Exception $e) { + // If something is wrong with matching the ip, we do nothing } + + return false; } /** From af5a14afc31474572e076c498cba47cb418a35de Mon Sep 17 00:00:00 2001 From: rodehoed <6515395+rodehoed@users.noreply.github.com> Date: Wed, 19 May 2021 09:01:45 +0200 Subject: [PATCH 093/478] Optimized the canPass() functions --- lib/Persistence/TrafficLimiter.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 34584b5..af6e497 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -122,7 +122,9 @@ class TrafficLimiter extends AbstractPersistence $range = \IPLib\Factory::rangeFromString(trim($ipRange)); // If $range is null something went wrong (possible invalid ip given in config). It's here becaue matches($range) does not accepts null vallue - if ($range == null) return false; + if ($range == null) { + return false; + } // Ip-lib does throws and exception when something goes wrong, if so we want to catch it and set contained to false try { From 89f6f0051dd56b7a9390e70df03271fe3d1f1353 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 May 2021 09:21:01 +0200 Subject: [PATCH 094/478] imported mlocati/ip-lib version 1.14.0 --- composer.json | 4 +- composer.lock | 12 +- vendor/composer/autoload_classmap.php | 14 + vendor/composer/autoload_psr4.php | 1 + vendor/composer/autoload_static.php | 19 + vendor/mlocati/ip-lib/ip-lib.php | 13 + .../ip-lib/src/Address/AddressInterface.php | 125 ++++ .../ip-lib/src/Address/AssignedRange.php | 138 +++++ vendor/mlocati/ip-lib/src/Address/IPv4.php | 465 ++++++++++++++ vendor/mlocati/ip-lib/src/Address/IPv6.php | 568 ++++++++++++++++++ vendor/mlocati/ip-lib/src/Address/Type.php | 42 ++ vendor/mlocati/ip-lib/src/Factory.php | 205 +++++++ .../ip-lib/src/Range/AbstractRange.php | 95 +++ vendor/mlocati/ip-lib/src/Range/Pattern.php | 275 +++++++++ .../ip-lib/src/Range/RangeInterface.php | 120 ++++ vendor/mlocati/ip-lib/src/Range/Single.php | 226 +++++++ vendor/mlocati/ip-lib/src/Range/Subnet.php | 305 ++++++++++ vendor/mlocati/ip-lib/src/Range/Type.php | 150 +++++ .../mlocati/ip-lib/src/Service/BinaryMath.php | 118 ++++ .../Service/RangesFromBounradyCalculator.php | 161 +++++ 20 files changed, 3048 insertions(+), 8 deletions(-) create mode 100644 vendor/mlocati/ip-lib/ip-lib.php create mode 100644 vendor/mlocati/ip-lib/src/Address/AddressInterface.php create mode 100644 vendor/mlocati/ip-lib/src/Address/AssignedRange.php create mode 100644 vendor/mlocati/ip-lib/src/Address/IPv4.php create mode 100644 vendor/mlocati/ip-lib/src/Address/IPv6.php create mode 100644 vendor/mlocati/ip-lib/src/Address/Type.php create mode 100644 vendor/mlocati/ip-lib/src/Factory.php create mode 100644 vendor/mlocati/ip-lib/src/Range/AbstractRange.php create mode 100644 vendor/mlocati/ip-lib/src/Range/Pattern.php create mode 100644 vendor/mlocati/ip-lib/src/Range/RangeInterface.php create mode 100644 vendor/mlocati/ip-lib/src/Range/Single.php create mode 100644 vendor/mlocati/ip-lib/src/Range/Subnet.php create mode 100644 vendor/mlocati/ip-lib/src/Range/Type.php create mode 100644 vendor/mlocati/ip-lib/src/Service/BinaryMath.php create mode 100644 vendor/mlocati/ip-lib/src/Service/RangesFromBounradyCalculator.php diff --git a/composer.json b/composer.json index ff79af9..98aaedd 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "php" : "^5.6.0 || ^7.0 || ^8.0", "paragonie/random_compat" : "2.0.19", "yzalis/identicon" : "2.0.0", - "mlocati/ip-lib": "^1.14" + "mlocati/ip-lib" : "1.14.0" }, "require-dev" : { "phpunit/phpunit" : "^4.6 || ^5.0" @@ -40,4 +40,4 @@ "config" : { "autoloader-suffix" : "DontChange" } -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index 66a9c8a..20b0732 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8138b0e4bb3fcaab9ca4c02bbe35d891", + "content-hash": "c3aa7487ba976536cd3db9a60473ce67", "packages": [ { "name": "mlocati/ip-lib", @@ -1495,16 +1495,16 @@ }, { "name": "symfony/yaml", - "version": "v4.4.21", + "version": "v4.4.24", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "3871c720871029f008928244e56cf43497da7e9d" + "reference": "8b6d1b97521e2f125039b3fcb4747584c6dfa0ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/3871c720871029f008928244e56cf43497da7e9d", - "reference": "3871c720871029f008928244e56cf43497da7e9d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/8b6d1b97521e2f125039b3fcb4747584c6dfa0ef", + "reference": "8b6d1b97521e2f125039b3fcb4747584c6dfa0ef", "shasum": "" }, "require": { @@ -1559,7 +1559,7 @@ "type": "tidelift" } ], - "time": "2021-03-05T17:58:50+00:00" + "time": "2021-05-16T09:52:47+00:00" }, { "name": "webmozart/assert", diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index b98b672..81358f5 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -6,6 +6,20 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'IPLib\\Address\\AddressInterface' => $vendorDir . '/mlocati/ip-lib/src/Address/AddressInterface.php', + 'IPLib\\Address\\AssignedRange' => $vendorDir . '/mlocati/ip-lib/src/Address/AssignedRange.php', + 'IPLib\\Address\\IPv4' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv4.php', + 'IPLib\\Address\\IPv6' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv6.php', + 'IPLib\\Address\\Type' => $vendorDir . '/mlocati/ip-lib/src/Address/Type.php', + 'IPLib\\Factory' => $vendorDir . '/mlocati/ip-lib/src/Factory.php', + 'IPLib\\Range\\AbstractRange' => $vendorDir . '/mlocati/ip-lib/src/Range/AbstractRange.php', + 'IPLib\\Range\\Pattern' => $vendorDir . '/mlocati/ip-lib/src/Range/Pattern.php', + 'IPLib\\Range\\RangeInterface' => $vendorDir . '/mlocati/ip-lib/src/Range/RangeInterface.php', + 'IPLib\\Range\\Single' => $vendorDir . '/mlocati/ip-lib/src/Range/Single.php', + 'IPLib\\Range\\Subnet' => $vendorDir . '/mlocati/ip-lib/src/Range/Subnet.php', + 'IPLib\\Range\\Type' => $vendorDir . '/mlocati/ip-lib/src/Range/Type.php', + 'IPLib\\Service\\BinaryMath' => $vendorDir . '/mlocati/ip-lib/src/Service/BinaryMath.php', + 'IPLib\\Service\\RangesFromBounradyCalculator' => $vendorDir . '/mlocati/ip-lib/src/Service/RangesFromBounradyCalculator.php', 'Identicon\\Generator\\BaseGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/BaseGenerator.php', 'Identicon\\Generator\\GdGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/GdGenerator.php', 'Identicon\\Generator\\GeneratorInterface' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/GeneratorInterface.php', diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 313097d..9bd3702 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -8,4 +8,5 @@ $baseDir = dirname($vendorDir); return array( 'PrivateBin\\' => array($baseDir . '/lib'), 'Identicon\\' => array($vendorDir . '/yzalis/identicon/src/Identicon'), + 'IPLib\\' => array($vendorDir . '/mlocati/ip-lib/src'), ); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 604d6bb..9197c94 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -18,6 +18,7 @@ class ComposerStaticInitDontChange 'I' => array ( 'Identicon\\' => 10, + 'IPLib\\' => 6, ), ); @@ -30,9 +31,27 @@ class ComposerStaticInitDontChange array ( 0 => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon', ), + 'IPLib\\' => + array ( + 0 => __DIR__ . '/..' . '/mlocati/ip-lib/src', + ), ); public static $classMap = array ( + 'IPLib\\Address\\AddressInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AddressInterface.php', + 'IPLib\\Address\\AssignedRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AssignedRange.php', + 'IPLib\\Address\\IPv4' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv4.php', + 'IPLib\\Address\\IPv6' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv6.php', + 'IPLib\\Address\\Type' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/Type.php', + 'IPLib\\Factory' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Factory.php', + 'IPLib\\Range\\AbstractRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/AbstractRange.php', + 'IPLib\\Range\\Pattern' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Pattern.php', + 'IPLib\\Range\\RangeInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/RangeInterface.php', + 'IPLib\\Range\\Single' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Single.php', + 'IPLib\\Range\\Subnet' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Subnet.php', + 'IPLib\\Range\\Type' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Type.php', + 'IPLib\\Service\\BinaryMath' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/BinaryMath.php', + 'IPLib\\Service\\RangesFromBounradyCalculator' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/RangesFromBounradyCalculator.php', 'Identicon\\Generator\\BaseGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/BaseGenerator.php', 'Identicon\\Generator\\GdGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/GdGenerator.php', 'Identicon\\Generator\\GeneratorInterface' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/GeneratorInterface.php', diff --git a/vendor/mlocati/ip-lib/ip-lib.php b/vendor/mlocati/ip-lib/ip-lib.php new file mode 100644 index 0000000..c34d0b4 --- /dev/null +++ b/vendor/mlocati/ip-lib/ip-lib.php @@ -0,0 +1,13 @@ +range = $range; + $this->type = $type; + $this->exceptions = $exceptions; + } + + /** + * Get the range definition. + * + * @return \IPLib\Range\RangeInterface + */ + public function getRange() + { + return $this->range; + } + + /** + * Get the range type. + * + * @return int one of the \IPLib\Range\Type::T_ constants + */ + public function getType() + { + return $this->type; + } + + /** + * Get the list of exceptions for this range type. + * + * @return \IPLib\Address\AssignedRange[] + */ + public function getExceptions() + { + return $this->exceptions; + } + + /** + * Get the assigned type for a specific address. + * + * @param \IPLib\Address\AddressInterface $address + * + * @return int|null return NULL of the address is outside this address; a \IPLib\Range\Type::T_ constant otherwise + */ + public function getAddressType(AddressInterface $address) + { + $result = null; + if ($this->range->contains($address)) { + foreach ($this->exceptions as $exception) { + $result = $exception->getAddressType($address); + if ($result !== null) { + break; + } + } + if ($result === null) { + $result = $this->type; + } + } + + return $result; + } + + /** + * Get the assigned type for a specific address range. + * + * @param \IPLib\Range\RangeInterface $range + * + * @return int|false|null return NULL of the range is fully outside this range; false if it's partly crosses this range (or it contains mixed types); a \IPLib\Range\Type::T_ constant otherwise + */ + public function getRangeType(RangeInterface $range) + { + $myStart = $this->range->getComparableStartString(); + $rangeEnd = $range->getComparableEndString(); + if ($myStart > $rangeEnd) { + $result = null; + } else { + $myEnd = $this->range->getComparableEndString(); + $rangeStart = $range->getComparableStartString(); + if ($myEnd < $rangeStart) { + $result = null; + } elseif ($rangeStart < $myStart || $rangeEnd > $myEnd) { + $result = false; + } else { + $result = null; + foreach ($this->exceptions as $exception) { + $result = $exception->getRangeType($range); + if ($result !== null) { + break; + } + } + if ($result === null) { + $result = $this->getType(); + } + } + } + + return $result; + } +} diff --git a/vendor/mlocati/ip-lib/src/Address/IPv4.php b/vendor/mlocati/ip-lib/src/Address/IPv4.php new file mode 100644 index 0000000..d31338e --- /dev/null +++ b/vendor/mlocati/ip-lib/src/Address/IPv4.php @@ -0,0 +1,465 @@ +address = $address; + $this->bytes = null; + $this->rangeType = null; + $this->comparableString = null; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::__toString() + */ + public function __toString() + { + return $this->address; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getNumberOfBits() + */ + public static function getNumberOfBits() + { + return 32; + } + + /** + * Parse a string and returns an IPv4 instance if the string is valid, or null otherwise. + * + * @param string|mixed $address the address to parse + * @param bool $mayIncludePort set to false to avoid parsing addresses with ports + * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses + * + * @return static|null + */ + public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false) + { + if (!is_string($address) || !strpos($address, '.')) { + return null; + } + $rxChunk = '0?[0-9]{1,3}'; + if ($supportNonDecimalIPv4) { + $rxChunk = "(?:0[Xx]0*[0-9A-Fa-f]{1,2})|(?:{$rxChunk})"; + } + $rx = "0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})"; + if ($mayIncludePort) { + $rx .= '(?::\d+)?'; + } + $matches = null; + if (!preg_match('/^' . $rx . '$/', $address, $matches)) { + return null; + } + $nums = array(); + for ($i = 1; $i <= 4; $i++) { + $s = $matches[$i]; + if ($supportNonDecimalIPv4) { + if (stripos($s, '0x') === 0) { + $n = hexdec(substr($s, 2)); + } elseif ($s[0] === '0') { + if (!preg_match('/^[0-7]+$/', $s)) { + return null; + } + $n = octdec(substr($s, 1)); + } else { + $n = (int) $s; + } + } else { + $n = (int) $s; + } + if ($n < 0 || $n > 255) { + return null; + } + $nums[] = (string) $n; + } + + return new static(implode('.', $nums)); + } + + /** + * Parse an array of bytes and returns an IPv4 instance if the array is valid, or null otherwise. + * + * @param int[]|array $bytes + * + * @return static|null + */ + public static function fromBytes(array $bytes) + { + $result = null; + if (count($bytes) === 4) { + $chunks = array_map( + function ($byte) { + return (is_int($byte) && $byte >= 0 && $byte <= 255) ? (string) $byte : false; + }, + $bytes + ); + if (in_array(false, $chunks, true) === false) { + $result = new static(implode('.', $chunks)); + } + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::toString() + */ + public function toString($long = false) + { + if ($long) { + return $this->getComparableString(); + } + + return $this->address; + } + + /** + * Get the octal representation of this IP address. + * + * @param bool $long + * + * @return string + * + * @example if $long == false: if the decimal representation is '0.7.8.255': '0.7.010.0377' + * @example if $long == true: if the decimal representation is '0.7.8.255': '0000.0007.0010.0377' + */ + public function toOctal($long = false) + { + $chunks = array(); + foreach ($this->getBytes() as $byte) { + if ($long) { + $chunks[] = sprintf('%04o', $byte); + } else { + $chunks[] = '0' . decoct($byte); + } + } + + return implode('.', $chunks); + } + + /** + * Get the hexadecimal representation of this IP address. + * + * @param bool $long + * + * @return string + * + * @example if $long == false: if the decimal representation is '0.9.10.255': '0.9.0xa.0xff' + * @example if $long == true: if the decimal representation is '0.9.10.255': '0x00.0x09.0x0a.0xff' + */ + public function toHexadecimal($long = false) + { + $chunks = array(); + foreach ($this->getBytes() as $byte) { + if ($long) { + $chunks[] = sprintf('0x%02x', $byte); + } else { + $chunks[] = '0x' . dechex($byte); + } + } + + return implode('.', $chunks); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getBytes() + */ + public function getBytes() + { + if ($this->bytes === null) { + $this->bytes = array_map( + function ($chunk) { + return (int) $chunk; + }, + explode('.', $this->address) + ); + } + + return $this->bytes; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getBits() + */ + public function getBits() + { + $parts = array(); + foreach ($this->getBytes() as $byte) { + $parts[] = sprintf('%08b', $byte); + } + + return implode('', $parts); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getAddressType() + */ + public function getAddressType() + { + return Type::T_IPv4; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType() + */ + public static function getDefaultReservedRangeType() + { + return RangeType::T_PUBLIC; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getReservedRanges() + */ + public static function getReservedRanges() + { + if (self::$reservedRanges === null) { + $reservedRanges = array(); + foreach (array( + // RFC 5735 + '0.0.0.0/8' => array(RangeType::T_THISNETWORK, array('0.0.0.0/32' => RangeType::T_UNSPECIFIED)), + // RFC 5735 + '10.0.0.0/8' => array(RangeType::T_PRIVATENETWORK), + // RFC 6598 + '100.64.0.0/10' => array(RangeType::T_CGNAT), + // RFC 5735 + '127.0.0.0/8' => array(RangeType::T_LOOPBACK), + // RFC 5735 + '169.254.0.0/16' => array(RangeType::T_LINKLOCAL), + // RFC 5735 + '172.16.0.0/12' => array(RangeType::T_PRIVATENETWORK), + // RFC 5735 + '192.0.0.0/24' => array(RangeType::T_RESERVED), + // RFC 5735 + '192.0.2.0/24' => array(RangeType::T_RESERVED), + // RFC 5735 + '192.88.99.0/24' => array(RangeType::T_ANYCASTRELAY), + // RFC 5735 + '192.168.0.0/16' => array(RangeType::T_PRIVATENETWORK), + // RFC 5735 + '198.18.0.0/15' => array(RangeType::T_RESERVED), + // RFC 5735 + '198.51.100.0/24' => array(RangeType::T_RESERVED), + // RFC 5735 + '203.0.113.0/24' => array(RangeType::T_RESERVED), + // RFC 5735 + '224.0.0.0/4' => array(RangeType::T_MULTICAST), + // RFC 5735 + '240.0.0.0/4' => array(RangeType::T_RESERVED, array('255.255.255.255/32' => RangeType::T_LIMITEDBROADCAST)), + ) as $range => $data) { + $exceptions = array(); + if (isset($data[1])) { + foreach ($data[1] as $exceptionRange => $exceptionType) { + $exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType); + } + } + $reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions); + } + self::$reservedRanges = $reservedRanges; + } + + return self::$reservedRanges; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getRangeType() + */ + public function getRangeType() + { + if ($this->rangeType === null) { + $rangeType = null; + foreach (static::getReservedRanges() as $reservedRange) { + $rangeType = $reservedRange->getAddressType($this); + if ($rangeType !== null) { + break; + } + } + $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType; + } + + return $this->rangeType; + } + + /** + * Create an IPv6 representation of this address (in 6to4 notation). + * + * @return \IPLib\Address\IPv6 + */ + public function toIPv6() + { + $myBytes = $this->getBytes(); + + return IPv6::fromString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::'); + } + + /** + * Create an IPv6 representation of this address (in IPv6 IPv4-mapped notation). + * + * @return \IPLib\Address\IPv6 + */ + public function toIPv6IPv4Mapped() + { + return IPv6::fromBytes(array_merge(array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff), $this->getBytes())); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getComparableString() + */ + public function getComparableString() + { + if ($this->comparableString === null) { + $chunks = array(); + foreach ($this->getBytes() as $byte) { + $chunks[] = sprintf('%03d', $byte); + } + $this->comparableString = implode('.', $chunks); + } + + return $this->comparableString; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::matches() + */ + public function matches(RangeInterface $range) + { + return $range->contains($this); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getNextAddress() + */ + public function getNextAddress() + { + $overflow = false; + $bytes = $this->getBytes(); + for ($i = count($bytes) - 1; $i >= 0; $i--) { + if ($bytes[$i] === 255) { + if ($i === 0) { + $overflow = true; + break; + } + $bytes[$i] = 0; + } else { + $bytes[$i]++; + break; + } + } + + return $overflow ? null : static::fromBytes($bytes); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getPreviousAddress() + */ + public function getPreviousAddress() + { + $overflow = false; + $bytes = $this->getBytes(); + for ($i = count($bytes) - 1; $i >= 0; $i--) { + if ($bytes[$i] === 0) { + if ($i === 0) { + $overflow = true; + break; + } + $bytes[$i] = 255; + } else { + $bytes[$i]--; + break; + } + } + + return $overflow ? null : static::fromBytes($bytes); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName() + */ + public function getReverseDNSLookupName() + { + return implode( + '.', + array_reverse($this->getBytes()) + ) . '.in-addr.arpa'; + } +} diff --git a/vendor/mlocati/ip-lib/src/Address/IPv6.php b/vendor/mlocati/ip-lib/src/Address/IPv6.php new file mode 100644 index 0000000..3666487 --- /dev/null +++ b/vendor/mlocati/ip-lib/src/Address/IPv6.php @@ -0,0 +1,568 @@ +longAddress = $longAddress; + $this->shortAddress = null; + $this->bytes = null; + $this->words = null; + $this->rangeType = null; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::__toString() + */ + public function __toString() + { + return $this->toString(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getNumberOfBits() + */ + public static function getNumberOfBits() + { + return 128; + } + + /** + * Parse a string and returns an IPv6 instance if the string is valid, or null otherwise. + * + * @param string|mixed $address the address to parse + * @param bool $mayIncludePort set to false to avoid parsing addresses with ports + * @param bool $mayIncludeZoneID set to false to avoid parsing addresses with zone IDs (see RFC 4007) + * + * @return static|null + */ + public static function fromString($address, $mayIncludePort = true, $mayIncludeZoneID = true) + { + $result = null; + if (is_string($address) && strpos($address, ':') !== false && strpos($address, ':::') === false) { + $matches = null; + if ($mayIncludePort && $address[0] === '[' && preg_match('/^\[(.+)]:\d+$/', $address, $matches)) { + $address = $matches[1]; + } + if ($mayIncludeZoneID) { + $percentagePos = strpos($address, '%'); + if ($percentagePos > 0) { + $address = substr($address, 0, $percentagePos); + } + } + if (preg_match('/^((?:[0-9a-f]*:+)+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i', $address, $matches)) { + $address6 = static::fromString($matches[1] . '0:0', false); + if ($address6 !== null) { + $address4 = IPv4::fromString($matches[2], false); + if ($address4 !== null) { + $bytes4 = $address4->getBytes(); + $address6->longAddress = substr($address6->longAddress, 0, -9) . sprintf('%02x%02x:%02x%02x', $bytes4[0], $bytes4[1], $bytes4[2], $bytes4[3]); + $result = $address6; + } + } + } else { + if (strpos($address, '::') === false) { + $chunks = explode(':', $address); + } else { + $chunks = array(); + $parts = explode('::', $address); + if (count($parts) === 2) { + $before = ($parts[0] === '') ? array() : explode(':', $parts[0]); + $after = ($parts[1] === '') ? array() : explode(':', $parts[1]); + $missing = 8 - count($before) - count($after); + if ($missing >= 0) { + $chunks = $before; + if ($missing !== 0) { + $chunks = array_merge($chunks, array_fill(0, $missing, '0')); + } + $chunks = array_merge($chunks, $after); + } + } + } + if (count($chunks) === 8) { + $nums = array_map( + function ($chunk) { + return preg_match('/^[0-9A-Fa-f]{1,4}$/', $chunk) ? hexdec($chunk) : false; + }, + $chunks + ); + if (!in_array(false, $nums, true)) { + $longAddress = implode( + ':', + array_map( + function ($num) { + return sprintf('%04x', $num); + }, + $nums + ) + ); + $result = new static($longAddress); + } + } + } + } + + return $result; + } + + /** + * Parse an array of bytes and returns an IPv6 instance if the array is valid, or null otherwise. + * + * @param int[]|array $bytes + * + * @return static|null + */ + public static function fromBytes(array $bytes) + { + $result = null; + if (count($bytes) === 16) { + $address = ''; + for ($i = 0; $i < 16; $i++) { + if ($i !== 0 && $i % 2 === 0) { + $address .= ':'; + } + $byte = $bytes[$i]; + if (is_int($byte) && $byte >= 0 && $byte <= 255) { + $address .= sprintf('%02x', $byte); + } else { + $address = null; + break; + } + } + if ($address !== null) { + $result = new static($address); + } + } + + return $result; + } + + /** + * Parse an array of words and returns an IPv6 instance if the array is valid, or null otherwise. + * + * @param int[]|array $words + * + * @return static|null + */ + public static function fromWords(array $words) + { + $result = null; + if (count($words) === 8) { + $chunks = array(); + for ($i = 0; $i < 8; $i++) { + $word = $words[$i]; + if (is_int($word) && $word >= 0 && $word <= 0xffff) { + $chunks[] = sprintf('%04x', $word); + } else { + $chunks = null; + break; + } + } + if ($chunks !== null) { + $result = new static(implode(':', $chunks)); + } + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::toString() + */ + public function toString($long = false) + { + if ($long) { + $result = $this->longAddress; + } else { + if ($this->shortAddress === null) { + if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) { + $lastBytes = array_slice($this->getBytes(), -4); + $this->shortAddress = '::ffff:' . implode('.', $lastBytes); + } else { + $chunks = array_map( + function ($word) { + return dechex($word); + }, + $this->getWords() + ); + $shortAddress = implode(':', $chunks); + $matches = null; + for ($i = 8; $i > 1; $i--) { + $search = '(?:^|:)' . rtrim(str_repeat('0:', $i), ':') . '(?:$|:)'; + if (preg_match('/^(.*?)' . $search . '(.*)$/', $shortAddress, $matches)) { + $shortAddress = $matches[1] . '::' . $matches[2]; + break; + } + } + $this->shortAddress = $shortAddress; + } + } + $result = $this->shortAddress; + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getBytes() + */ + public function getBytes() + { + if ($this->bytes === null) { + $bytes = array(); + foreach ($this->getWords() as $word) { + $bytes[] = $word >> 8; + $bytes[] = $word & 0xff; + } + $this->bytes = $bytes; + } + + return $this->bytes; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getBits() + */ + public function getBits() + { + $parts = array(); + foreach ($this->getBytes() as $byte) { + $parts[] = sprintf('%08b', $byte); + } + + return implode('', $parts); + } + + /** + * Get the word list of the IP address. + * + * @return int[] + */ + public function getWords() + { + if ($this->words === null) { + $this->words = array_map( + function ($chunk) { + return hexdec($chunk); + }, + explode(':', $this->longAddress) + ); + } + + return $this->words; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getAddressType() + */ + public function getAddressType() + { + return Type::T_IPv6; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType() + */ + public static function getDefaultReservedRangeType() + { + return RangeType::T_RESERVED; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getReservedRanges() + */ + public static function getReservedRanges() + { + if (self::$reservedRanges === null) { + $reservedRanges = array(); + foreach (array( + // RFC 4291 + '::/128' => array(RangeType::T_UNSPECIFIED), + // RFC 4291 + '::1/128' => array(RangeType::T_LOOPBACK), + // RFC 4291 + '100::/8' => array(RangeType::T_DISCARD, array('100::/64' => RangeType::T_DISCARDONLY)), + //'2002::/16' => array(RangeType::), + // RFC 4291 + '2000::/3' => array(RangeType::T_PUBLIC), + // RFC 4193 + 'fc00::/7' => array(RangeType::T_PRIVATENETWORK), + // RFC 4291 + 'fe80::/10' => array(RangeType::T_LINKLOCAL_UNICAST), + // RFC 4291 + 'ff00::/8' => array(RangeType::T_MULTICAST), + // RFC 4291 + //'::/8' => array(RangeType::T_RESERVED), + // RFC 4048 + //'200::/7' => array(RangeType::T_RESERVED), + // RFC 4291 + //'400::/6' => array(RangeType::T_RESERVED), + // RFC 4291 + //'800::/5' => array(RangeType::T_RESERVED), + // RFC 4291 + //'1000::/4' => array(RangeType::T_RESERVED), + // RFC 4291 + //'4000::/3' => array(RangeType::T_RESERVED), + // RFC 4291 + //'6000::/3' => array(RangeType::T_RESERVED), + // RFC 4291 + //'8000::/3' => array(RangeType::T_RESERVED), + // RFC 4291 + //'a000::/3' => array(RangeType::T_RESERVED), + // RFC 4291 + //'c000::/3' => array(RangeType::T_RESERVED), + // RFC 4291 + //'e000::/4' => array(RangeType::T_RESERVED), + // RFC 4291 + //'f000::/5' => array(RangeType::T_RESERVED), + // RFC 4291 + //'f800::/6' => array(RangeType::T_RESERVED), + // RFC 4291 + //'fe00::/9' => array(RangeType::T_RESERVED), + // RFC 3879 + //'fec0::/10' => array(RangeType::T_RESERVED), + ) as $range => $data) { + $exceptions = array(); + if (isset($data[1])) { + foreach ($data[1] as $exceptionRange => $exceptionType) { + $exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType); + } + } + $reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions); + } + self::$reservedRanges = $reservedRanges; + } + + return self::$reservedRanges; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getRangeType() + */ + public function getRangeType() + { + if ($this->rangeType === null) { + $ipv4 = $this->toIPv4(); + if ($ipv4 !== null) { + $this->rangeType = $ipv4->getRangeType(); + } else { + $rangeType = null; + foreach (static::getReservedRanges() as $reservedRange) { + $rangeType = $reservedRange->getAddressType($this); + if ($rangeType !== null) { + break; + } + } + $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType; + } + } + + return $this->rangeType; + } + + /** + * Create an IPv4 representation of this address (if possible, otherwise returns null). + * + * @return \IPLib\Address\IPv4|null + */ + public function toIPv4() + { + if (strpos($this->longAddress, '2002:') === 0) { + // 6to4 + return IPv4::fromBytes(array_slice($this->getBytes(), 2, 4)); + } + if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) { + // IPv4-mapped IPv6 addresses + return IPv4::fromBytes(array_slice($this->getBytes(), -4)); + } + + return null; + } + + /** + * Render this IPv6 address in the "mixed" IPv6 (first 12 bytes) + IPv4 (last 4 bytes) mixed syntax. + * + * @param bool $ipV6Long render the IPv6 part in "long" format? + * @param bool $ipV4Long render the IPv4 part in "long" format? + * + * @return string + * + * @example '::13.1.68.3' + * @example '0000:0000:0000:0000:0000:0000:13.1.68.3' when $ipV6Long is true + * @example '::013.001.068.003' when $ipV4Long is true + * @example '0000:0000:0000:0000:0000:0000:013.001.068.003' when $ipV6Long and $ipV4Long are true + * + * @see https://tools.ietf.org/html/rfc4291#section-2.2 point 3. + */ + public function toMixedIPv6IPv4String($ipV6Long = false, $ipV4Long = false) + { + $myBytes = $this->getBytes(); + $ipv6Bytes = array_merge(array_slice($myBytes, 0, 12), array(0xff, 0xff, 0xff, 0xff)); + $ipv6String = static::fromBytes($ipv6Bytes)->toString($ipV6Long); + $ipv4Bytes = array_slice($myBytes, 12, 4); + $ipv4String = IPv4::fromBytes($ipv4Bytes)->toString($ipV4Long); + + return preg_replace('/((ffff:ffff)|(\d+(\.\d+){3}))$/i', $ipv4String, $ipv6String); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getComparableString() + */ + public function getComparableString() + { + return $this->longAddress; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::matches() + */ + public function matches(RangeInterface $range) + { + return $range->contains($this); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getNextAddress() + */ + public function getNextAddress() + { + $overflow = false; + $words = $this->getWords(); + for ($i = count($words) - 1; $i >= 0; $i--) { + if ($words[$i] === 0xffff) { + if ($i === 0) { + $overflow = true; + break; + } + $words[$i] = 0; + } else { + $words[$i]++; + break; + } + } + + return $overflow ? null : static::fromWords($words); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getPreviousAddress() + */ + public function getPreviousAddress() + { + $overflow = false; + $words = $this->getWords(); + for ($i = count($words) - 1; $i >= 0; $i--) { + if ($words[$i] === 0) { + if ($i === 0) { + $overflow = true; + break; + } + $words[$i] = 0xffff; + } else { + $words[$i]--; + break; + } + } + + return $overflow ? null : static::fromWords($words); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName() + */ + public function getReverseDNSLookupName() + { + return implode( + '.', + array_reverse(str_split(str_replace(':', '', $this->toString(true)), 1)) + ) . '.ip6.arpa'; + } +} diff --git a/vendor/mlocati/ip-lib/src/Address/Type.php b/vendor/mlocati/ip-lib/src/Address/Type.php new file mode 100644 index 0000000..06b0753 --- /dev/null +++ b/vendor/mlocati/ip-lib/src/Address/Type.php @@ -0,0 +1,42 @@ +getNumberOfBits())); + } + $numberOfBits = $from->getNumberOfBits(); + if ($to->getNumberOfBits() !== $numberOfBits) { + return null; + } + $calculator = new RangesFromBounradyCalculator($numberOfBits); + + return $calculator->getRanges($from, $to); + } + + /** + * @param \IPLib\Address\AddressInterface $from + * @param \IPLib\Address\AddressInterface $to + * + * @return \IPLib\Range\RangeInterface|null + */ + protected static function rangeFromBoundaryAddresses(AddressInterface $from = null, AddressInterface $to = null) + { + if ($from === null && $to === null) { + $result = null; + } elseif ($to === null) { + $result = Range\Single::fromAddress($from); + } elseif ($from === null) { + $result = Range\Single::fromAddress($to); + } else { + $result = null; + $addressType = $from->getAddressType(); + if ($addressType === $to->getAddressType()) { + $cmp = strcmp($from->getComparableString(), $to->getComparableString()); + if ($cmp === 0) { + $result = Range\Single::fromAddress($from); + } else { + if ($cmp > 0) { + list($from, $to) = array($to, $from); + } + $fromBytes = $from->getBytes(); + $toBytes = $to->getBytes(); + $numBytes = count($fromBytes); + $sameBits = 0; + for ($byteIndex = 0; $byteIndex < $numBytes; $byteIndex++) { + $fromByte = $fromBytes[$byteIndex]; + $toByte = $toBytes[$byteIndex]; + if ($fromByte === $toByte) { + $sameBits += 8; + } else { + $differentBitsInByte = decbin($fromByte ^ $toByte); + $sameBits += 8 - strlen($differentBitsInByte); + break; + } + } + $result = static::rangeFromString($from->toString(true) . '/' . (string) $sameBits); + } + } + } + + return $result; + } + + /** + * @param string|\IPLib\Address\AddressInterface $from + * @param string|\IPLib\Address\AddressInterface $to + * @param bool $supportNonDecimalIPv4 + * + * @return \IPLib\Address\AddressInterface[]|null[]|false[] + */ + private static function parseBoundaries($from, $to, $supportNonDecimalIPv4 = false) + { + $result = array(); + foreach (array('from', 'to') as $param) { + $value = $$param; + if (!($value instanceof AddressInterface)) { + $value = (string) $value; + if ($value === '') { + $value = null; + } else { + $value = static::addressFromString($value, true, true, $supportNonDecimalIPv4); + if ($value === null) { + $value = false; + } + } + } + $result[] = $value; + } + if ($result[0] && $result[1] && strcmp($result[0]->getComparableString(), $result[1]->getComparableString()) > 0) { + $result = array($result[1], $result[0]); + } + + return $result; + } +} diff --git a/vendor/mlocati/ip-lib/src/Range/AbstractRange.php b/vendor/mlocati/ip-lib/src/Range/AbstractRange.php new file mode 100644 index 0000000..1da94d0 --- /dev/null +++ b/vendor/mlocati/ip-lib/src/Range/AbstractRange.php @@ -0,0 +1,95 @@ +rangeType === null) { + $addressType = $this->getAddressType(); + if ($addressType === AddressType::T_IPv6 && Subnet::get6to4()->containsRange($this)) { + $this->rangeType = Factory::rangeFromBoundaries($this->fromAddress->toIPv4(), $this->toAddress->toIPv4())->getRangeType(); + } else { + switch ($addressType) { + case AddressType::T_IPv4: + $defaultType = IPv4::getDefaultReservedRangeType(); + $reservedRanges = IPv4::getReservedRanges(); + break; + case AddressType::T_IPv6: + $defaultType = IPv6::getDefaultReservedRangeType(); + $reservedRanges = IPv6::getReservedRanges(); + break; + default: + throw new \Exception('@todo'); // @codeCoverageIgnore + } + $rangeType = null; + foreach ($reservedRanges as $reservedRange) { + $rangeType = $reservedRange->getRangeType($this); + if ($rangeType !== null) { + break; + } + } + $this->rangeType = $rangeType === null ? $defaultType : $rangeType; + } + } + + return $this->rangeType === false ? null : $this->rangeType; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::contains() + */ + public function contains(AddressInterface $address) + { + $result = false; + if ($address->getAddressType() === $this->getAddressType()) { + $cmp = $address->getComparableString(); + $from = $this->getComparableStartString(); + if ($cmp >= $from) { + $to = $this->getComparableEndString(); + if ($cmp <= $to) { + $result = true; + } + } + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::containsRange() + */ + public function containsRange(RangeInterface $range) + { + $result = false; + if ($range->getAddressType() === $this->getAddressType()) { + $myStart = $this->getComparableStartString(); + $itsStart = $range->getComparableStartString(); + if ($itsStart >= $myStart) { + $myEnd = $this->getComparableEndString(); + $itsEnd = $range->getComparableEndString(); + if ($itsEnd <= $myEnd) { + $result = true; + } + } + } + + return $result; + } +} diff --git a/vendor/mlocati/ip-lib/src/Range/Pattern.php b/vendor/mlocati/ip-lib/src/Range/Pattern.php new file mode 100644 index 0000000..fc19d1e --- /dev/null +++ b/vendor/mlocati/ip-lib/src/Range/Pattern.php @@ -0,0 +1,275 @@ +fromAddress = $fromAddress; + $this->toAddress = $toAddress; + $this->asterisksCount = $asterisksCount; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::__toString() + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Try get the range instance starting from its string representation. + * + * @param string|mixed $range + * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses + * + * @return static|null + */ + public static function fromString($range, $supportNonDecimalIPv4 = false) + { + if (!is_string($range) || strpos($range, '*') === false) { + return null; + } + if ($range === '*.*.*.*') { + return new static(IPv4::fromString('0.0.0.0'), IPv4::fromString('255.255.255.255'), 4); + } + if ($range === '*:*:*:*:*:*:*:*') { + return new static(IPv6::fromString('::'), IPv6::fromString('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), 8); + } + $matches = null; + if (strpos($range, '.') !== false && preg_match('/^[^*]+((?:\.\*)+)$/', $range, $matches)) { + $asterisksCount = strlen($matches[1]) >> 1; + if ($asterisksCount > 0) { + $missingDots = 3 - substr_count($range, '.'); + if ($missingDots > 0) { + $range .= str_repeat('.*', $missingDots); + $asterisksCount += $missingDots; + } + } + $fromAddress = IPv4::fromString(str_replace('*', '0', $range), true, $supportNonDecimalIPv4); + if ($fromAddress === null) { + return null; + } + $fixedBytes = array_slice($fromAddress->getBytes(), 0, -$asterisksCount); + $otherBytes = array_fill(0, $asterisksCount, 255); + $toAddress = IPv4::fromBytes(array_merge($fixedBytes, $otherBytes)); + + return new static($fromAddress, $toAddress, $asterisksCount); + } + if (strpos($range, ':') !== false && preg_match('/^[^*]+((?::\*)+)$/', $range, $matches)) { + $asterisksCount = strlen($matches[1]) >> 1; + $fromAddress = IPv6::fromString(str_replace('*', '0', $range)); + if ($fromAddress === null) { + return null; + } + $fixedWords = array_slice($fromAddress->getWords(), 0, -$asterisksCount); + $otherWords = array_fill(0, $asterisksCount, 0xffff); + $toAddress = IPv6::fromWords(array_merge($fixedWords, $otherWords)); + + return new static($fromAddress, $toAddress, $asterisksCount); + } + + return null; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::toString() + */ + public function toString($long = false) + { + if ($this->asterisksCount === 0) { + return $this->fromAddress->toString($long); + } + switch (true) { + case $this->fromAddress instanceof \IPLib\Address\IPv4: + $chunks = explode('.', $this->fromAddress->toString()); + $chunks = array_slice($chunks, 0, -$this->asterisksCount); + $chunks = array_pad($chunks, 4, '*'); + $result = implode('.', $chunks); + break; + case $this->fromAddress instanceof \IPLib\Address\IPv6: + if ($long) { + $chunks = explode(':', $this->fromAddress->toString(true)); + $chunks = array_slice($chunks, 0, -$this->asterisksCount); + $chunks = array_pad($chunks, 8, '*'); + $result = implode(':', $chunks); + } elseif ($this->asterisksCount === 8) { + $result = '*:*:*:*:*:*:*:*'; + } else { + $bytes = $this->toAddress->getBytes(); + $bytes = array_slice($bytes, 0, -$this->asterisksCount * 2); + $bytes = array_pad($bytes, 16, 1); + $address = IPv6::fromBytes($bytes); + $before = substr($address->toString(false), 0, -strlen(':101') * $this->asterisksCount); + $result = $before . str_repeat(':*', $this->asterisksCount); + } + break; + default: + throw new \Exception('@todo'); // @codeCoverageIgnore + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getAddressType() + */ + public function getAddressType() + { + return $this->fromAddress->getAddressType(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getStartAddress() + */ + public function getStartAddress() + { + return $this->fromAddress; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getEndAddress() + */ + public function getEndAddress() + { + return $this->toAddress; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getComparableStartString() + */ + public function getComparableStartString() + { + return $this->fromAddress->getComparableString(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getComparableEndString() + */ + public function getComparableEndString() + { + return $this->toAddress->getComparableString(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::asSubnet() + */ + public function asSubnet() + { + switch ($this->getAddressType()) { + case AddressType::T_IPv4: + return new Subnet($this->getStartAddress(), $this->getEndAddress(), 8 * (4 - $this->asterisksCount)); + case AddressType::T_IPv6: + return new Subnet($this->getStartAddress(), $this->getEndAddress(), 16 * (8 - $this->asterisksCount)); + } + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::asPattern() + */ + public function asPattern() + { + return $this; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getSubnetMask() + */ + public function getSubnetMask() + { + if ($this->getAddressType() !== AddressType::T_IPv4) { + return null; + } + switch ($this->asterisksCount) { + case 0: + $bytes = array(255, 255, 255, 255); + break; + case 4: + $bytes = array(0, 0, 0, 0); + break; + default: + $bytes = array_pad(array_fill(0, 4 - $this->asterisksCount, 255), 4, 0); + break; + } + + return IPv4::fromBytes($bytes); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName() + */ + public function getReverseDNSLookupName() + { + return $this->asterisksCount === 0 ? array($this->getStartAddress()->getReverseDNSLookupName()) : $this->asSubnet()->getReverseDNSLookupName(); + } +} diff --git a/vendor/mlocati/ip-lib/src/Range/RangeInterface.php b/vendor/mlocati/ip-lib/src/Range/RangeInterface.php new file mode 100644 index 0000000..468df3e --- /dev/null +++ b/vendor/mlocati/ip-lib/src/Range/RangeInterface.php @@ -0,0 +1,120 @@ +address = $address; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::__toString() + */ + public function __toString() + { + return $this->address->__toString(); + } + + /** + * Try get the range instance starting from its string representation. + * + * @param string|mixed $range + * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses + * + * @return static|null + */ + public static function fromString($range, $supportNonDecimalIPv4 = false) + { + $result = null; + $address = Factory::addressFromString($range, true, true, $supportNonDecimalIPv4); + if ($address !== null) { + $result = new static($address); + } + + return $result; + } + + /** + * Create the range instance starting from an address instance. + * + * @param \IPLib\Address\AddressInterface $address + * + * @return static + */ + public static function fromAddress(AddressInterface $address) + { + return new static($address); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::toString() + */ + public function toString($long = false) + { + return $this->address->toString($long); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getAddressType() + */ + public function getAddressType() + { + return $this->address->getAddressType(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getRangeType() + */ + public function getRangeType() + { + return $this->address->getRangeType(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::contains() + */ + public function contains(AddressInterface $address) + { + $result = false; + if ($address->getAddressType() === $this->getAddressType()) { + if ($address->toString(false) === $this->address->toString(false)) { + $result = true; + } + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::containsRange() + */ + public function containsRange(RangeInterface $range) + { + $result = false; + if ($range->getAddressType() === $this->getAddressType()) { + if ($range->toString(false) === $this->toString(false)) { + $result = true; + } + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getStartAddress() + */ + public function getStartAddress() + { + return $this->address; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getEndAddress() + */ + public function getEndAddress() + { + return $this->address; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getComparableStartString() + */ + public function getComparableStartString() + { + return $this->address->getComparableString(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getComparableEndString() + */ + public function getComparableEndString() + { + return $this->address->getComparableString(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::asSubnet() + */ + public function asSubnet() + { + $networkPrefixes = array( + AddressType::T_IPv4 => 32, + AddressType::T_IPv6 => 128, + ); + + return new Subnet($this->address, $this->address, $networkPrefixes[$this->address->getAddressType()]); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::asPattern() + */ + public function asPattern() + { + return new Pattern($this->address, $this->address, 0); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getSubnetMask() + */ + public function getSubnetMask() + { + if ($this->getAddressType() !== AddressType::T_IPv4) { + return null; + } + + return IPv4::fromBytes(array(255, 255, 255, 255)); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName() + */ + public function getReverseDNSLookupName() + { + return array($this->getStartAddress()->getReverseDNSLookupName()); + } +} diff --git a/vendor/mlocati/ip-lib/src/Range/Subnet.php b/vendor/mlocati/ip-lib/src/Range/Subnet.php new file mode 100644 index 0000000..2ce166b --- /dev/null +++ b/vendor/mlocati/ip-lib/src/Range/Subnet.php @@ -0,0 +1,305 @@ +fromAddress = $fromAddress; + $this->toAddress = $toAddress; + $this->networkPrefix = $networkPrefix; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::__toString() + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Try get the range instance starting from its string representation. + * + * @param string|mixed $range + * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses + * + * @return static|null + */ + public static function fromString($range, $supportNonDecimalIPv4 = false) + { + if (!is_string($range)) { + return null; + } + $parts = explode('/', $range); + if (count($parts) !== 2) { + return null; + } + $address = Factory::addressFromString($parts[0], true, true, $supportNonDecimalIPv4); + if ($address === null) { + return null; + } + if (!preg_match('/^[0-9]{1,9}$/', $parts[1])) { + return null; + } + $networkPrefix = (int) $parts[1]; + $addressBytes = $address->getBytes(); + $totalBytes = count($addressBytes); + $numDifferentBits = $totalBytes * 8 - $networkPrefix; + if ($numDifferentBits < 0) { + return null; + } + $numSameBytes = $networkPrefix >> 3; + $sameBytes = array_slice($addressBytes, 0, $numSameBytes); + $differentBytesStart = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 0); + $differentBytesEnd = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 255); + $startSameBits = $networkPrefix % 8; + if ($startSameBits !== 0) { + $varyingByte = $addressBytes[$numSameBytes]; + $differentBytesStart[0] = $varyingByte & bindec(str_pad(str_repeat('1', $startSameBits), 8, '0', STR_PAD_RIGHT)); + $differentBytesEnd[0] = $differentBytesStart[0] + bindec(str_repeat('1', 8 - $startSameBits)); + } + + return new static( + Factory::addressFromBytes(array_merge($sameBytes, $differentBytesStart)), + Factory::addressFromBytes(array_merge($sameBytes, $differentBytesEnd)), + $networkPrefix + ); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::toString() + */ + public function toString($long = false) + { + return $this->fromAddress->toString($long) . '/' . $this->networkPrefix; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getAddressType() + */ + public function getAddressType() + { + return $this->fromAddress->getAddressType(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getStartAddress() + */ + public function getStartAddress() + { + return $this->fromAddress; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getEndAddress() + */ + public function getEndAddress() + { + return $this->toAddress; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getComparableStartString() + */ + public function getComparableStartString() + { + return $this->fromAddress->getComparableString(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getComparableEndString() + */ + public function getComparableEndString() + { + return $this->toAddress->getComparableString(); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::asSubnet() + */ + public function asSubnet() + { + return $this; + } + + /** + * Get the pattern (asterisk) representation (if applicable) of this range. + * + * @return \IPLib\Range\Pattern|null return NULL if this range can't be represented by a pattern notation + */ + public function asPattern() + { + $address = $this->getStartAddress(); + $networkPrefix = $this->getNetworkPrefix(); + switch ($address->getAddressType()) { + case AddressType::T_IPv4: + return $networkPrefix % 8 === 0 ? new Pattern($address, $address, 4 - $networkPrefix / 8) : null; + case AddressType::T_IPv6: + return $networkPrefix % 16 === 0 ? new Pattern($address, $address, 8 - $networkPrefix / 16) : null; + } + } + + /** + * Get the 6to4 address IPv6 address range. + * + * @return self + */ + public static function get6to4() + { + if (self::$sixToFour === null) { + self::$sixToFour = self::fromString('2002::/16'); + } + + return self::$sixToFour; + } + + /** + * Get subnet prefix. + * + * @return int + */ + public function getNetworkPrefix() + { + return $this->networkPrefix; + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getSubnetMask() + */ + public function getSubnetMask() + { + if ($this->getAddressType() !== AddressType::T_IPv4) { + return null; + } + $bytes = array(); + $prefix = $this->getNetworkPrefix(); + while ($prefix >= 8) { + $bytes[] = 255; + $prefix -= 8; + } + if ($prefix !== 0) { + $bytes[] = bindec(str_pad(str_repeat('1', $prefix), 8, '0')); + } + $bytes = array_pad($bytes, 4, 0); + + return IPv4::fromBytes($bytes); + } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName() + */ + public function getReverseDNSLookupName() + { + switch ($this->getAddressType()) { + case AddressType::T_IPv4: + $unitSize = 8; // bytes + $maxUnits = 4; + $isHex = false; + $rxUnit = '\d+'; + break; + case AddressType::T_IPv6: + $unitSize = 4; // nibbles + $maxUnits = 32; + $isHex = true; + $rxUnit = '[0-9A-Fa-f]'; + break; + } + $totBits = $unitSize * $maxUnits; + $prefixUnits = (int) ($this->networkPrefix / $unitSize); + $extraBits = ($totBits - $this->networkPrefix) % $unitSize; + if ($extraBits !== 0) { + $prefixUnits += 1; + } + $numVariants = 1 << $extraBits; + $result = array(); + $unitsToRemove = $maxUnits - $prefixUnits; + $initialPointer = preg_replace("/^(({$rxUnit})\.){{$unitsToRemove}}/", '', $this->getStartAddress()->getReverseDNSLookupName()); + $chunks = explode('.', $initialPointer, 2); + for ($index = 0; $index < $numVariants; $index++) { + if ($index !== 0) { + $chunks[0] = $isHex ? dechex(1 + hexdec($chunks[0])) : (string) (1 + (int) $chunks[0]); + } + $result[] = implode('.', $chunks); + } + + return $result; + } +} diff --git a/vendor/mlocati/ip-lib/src/Range/Type.php b/vendor/mlocati/ip-lib/src/Range/Type.php new file mode 100644 index 0000000..cfc268b --- /dev/null +++ b/vendor/mlocati/ip-lib/src/Range/Type.php @@ -0,0 +1,150 @@ +toSameLength($a, $b); + + return $a < $b ? -1 : ($a > $b ? 1 : 0); + } + + /** + * Add 1 to a non-negative integer represented in binary form. + * + * @param string $value + * + * @return string + */ + public function increment($value) + { + $lastZeroIndex = strrpos($value, '0'); + if ($lastZeroIndex === false) { + return '1' . str_repeat('0', strlen($value)); + } + + return ltrim(substr($value, 0, $lastZeroIndex), '0') . '1' . str_repeat('0', strlen($value) - $lastZeroIndex - 1); + } + + /** + * Calculate the bitwise AND of two non-negative integers represented in binary form. + * + * @param string $operand1 + * @param string $operand2 + * + * @return string + */ + public function andX($operand1, $operand2) + { + $operand1 = $this->reduce($operand1); + $operand2 = $this->reduce($operand2); + $numBits = min(strlen($operand1), strlen($operand2)); + $operand1 = substr(str_pad($operand1, $numBits, '0', STR_PAD_LEFT), -$numBits); + $operand2 = substr(str_pad($operand2, $numBits, '0', STR_PAD_LEFT), -$numBits); + $result = ''; + for ($index = 0; $index < $numBits; $index++) { + $result .= $operand1[$index] === '1' && $operand2[$index] === '1' ? '1' : '0'; + } + + return $this->reduce($result); + } + + /** + * Calculate the bitwise OR of two non-negative integers represented in binary form. + * + * @param string $operand1 + * @param string $operand2 + * + * @return string + */ + public function orX($operand1, $operand2) + { + list($operand1, $operand2, $numBits) = $this->toSameLength($operand1, $operand2); + $result = ''; + for ($index = 0; $index < $numBits; $index++) { + $result .= $operand1[$index] === '1' || $operand2[$index] === '1' ? '1' : '0'; + } + + return $result; + } + + /** + * Zero-padding of two non-negative integers represented in binary form, so that they have the same length. + * + * @param string $num1 + * @param string $num2 + * + * @return string[],int[] The first array element is $num1 (padded), the first array element is $num2 (padded), the third array element is the number of bits + */ + private function toSameLength($num1, $num2) + { + $num1 = $this->reduce($num1); + $num2 = $this->reduce($num2); + $numBits = max(strlen($num1), strlen($num2)); + + return array( + str_pad($num1, $numBits, '0', STR_PAD_LEFT), + str_pad($num2, $numBits, '0', STR_PAD_LEFT), + $numBits, + ); + } +} diff --git a/vendor/mlocati/ip-lib/src/Service/RangesFromBounradyCalculator.php b/vendor/mlocati/ip-lib/src/Service/RangesFromBounradyCalculator.php new file mode 100644 index 0000000..c1d29d6 --- /dev/null +++ b/vendor/mlocati/ip-lib/src/Service/RangesFromBounradyCalculator.php @@ -0,0 +1,161 @@ +math = new BinaryMath(); + $this->setNumBits($numBits); + } + + /** + * Calculate the subnets describing all (and only all) the addresses between two bouundaries. + * + * @param \IPLib\Address\AddressInterface $from + * @param \IPLib\Address\AddressInterface $to + * + * @return \IPLib\Range\Subnet[]|null return NULL if the two addresses have an invalid number of bits (that is, different from the one passed to the constructor of this class) + */ + public function getRanges(AddressInterface $from, AddressInterface $to) + { + if ($from->getNumberOfBits() !== $this->numBits || $to->getNumberOfBits() !== $this->numBits) { + return null; + } + if ($from->getComparableString() > $to->getComparableString()) { + list($from, $to) = array($to, $from); + } + $result = array(); + $this->calculate($this->math->reduce($from->getBits()), $this->math->reduce($to->getBits()), $this->numBits, $result); + + return $result; + } + + /** + * Set the number of bits used to represent addresses (32 for IPv4, 128 for IPv6). + * + * @param int $numBits + */ + private function setNumBits($numBits) + { + $numBits = (int) $numBits; + $masks = array(); + $unmasks = array(); + for ($bit = 0; $bit < $numBits; $bit++) { + $masks[$bit] = str_repeat('1', $numBits - $bit) . str_repeat('0', $bit); + $unmasks[$bit] = $bit === 0 ? '0' : str_repeat('1', $bit); + } + $this->numBits = $numBits; + $this->masks = $masks; + $this->unmasks = $unmasks; + } + + /** + * Calculate the subnets. + * + * @param string $start the start address (represented in reduced bit form) + * @param string $end the end address (represented in reduced bit form) + * @param int $position the number of bits in the mask we are comparing at this cycle + * @param \IPLib\Range\Subnet[] $result found ranges will be added to this variable + */ + private function calculate($start, $end, $position, array &$result) + { + if ($start === $end) { + $result[] = $this->subnetFromBits($start, $this->numBits); + + return; + } + for ($index = $position - 1; $index >= 0; $index--) { + $startMasked = $this->math->andX($start, $this->masks[$index]); + $endMasked = $this->math->andX($end, $this->masks[$index]); + if ($startMasked !== $endMasked) { + $position = $index; + break; + } + } + if ($startMasked === $start && $this->math->andX($this->math->increment($end), $this->unmasks[$position]) === '0') { + $result[] = $this->subnetFromBits($start, $this->numBits - 1 - $position); + + return; + } + $middleAddress = $this->math->orX($start, $this->unmasks[$position]); + $this->calculate($start, $middleAddress, $position, $result); + $this->calculate($this->math->increment($middleAddress), $end, $position, $result); + } + + /** + * Create an address instance starting from its bits. + * + * @param string $bits the bits of the address (represented in reduced bit form) + * + * @return \IPLib\Address\AddressInterface + */ + private function addressFromBits($bits) + { + $bits = str_pad($bits, $this->numBits, '0', STR_PAD_LEFT); + $bytes = array(); + foreach (explode("\n", trim(chunk_split($bits, 8, "\n"))) as $byteBits) { + $bytes[] = bindec($byteBits); + } + + return Factory::addressFromBytes($bytes); + } + + /** + * Create an range instance starting from the bits if the address and the length of the network prefix. + * + * @param string $bits the bits of the address (represented in reduced bit form) + * @param int $networkPrefix the length of the network prefix + * + * @return \IPLib\Range\Subnet + */ + private function subnetFromBits($bits, $networkPrefix) + { + $address = $this->addressFromBits($bits); + + return new Subnet($address, $address, $networkPrefix); + } +} From 3dd01b1f7035f9c774b6a2a3c5b2a3720a04975a Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 May 2021 10:59:47 +0200 Subject: [PATCH 095/478] testing IP exemption, handle corner cases found in testing --- composer.json | 2 +- composer.lock | 12 ++++++------ lib/Persistence/TrafficLimiter.php | 13 ++++++++----- tst/Persistence/TrafficLimiterTest.php | 15 +++++++++++++++ vendor/paragonie/random_compat/lib/random.php | 19 ++++++++++--------- .../random_compat/phpunit-autoload.php | 14 -------------- 6 files changed, 40 insertions(+), 35 deletions(-) delete mode 100644 vendor/paragonie/random_compat/phpunit-autoload.php diff --git a/composer.json b/composer.json index 98aaedd..a8e98aa 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ }, "require" : { "php" : "^5.6.0 || ^7.0 || ^8.0", - "paragonie/random_compat" : "2.0.19", + "paragonie/random_compat" : "2.0.20", "yzalis/identicon" : "2.0.0", "mlocati/ip-lib" : "1.14.0" }, diff --git a/composer.lock b/composer.lock index 20b0732..931c92f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c3aa7487ba976536cd3db9a60473ce67", + "content-hash": "217f0ba9bdac1014a332a8ba390be949", "packages": [ { "name": "mlocati/ip-lib", @@ -76,16 +76,16 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.19", + "version": "v2.0.20", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "446fc9faa5c2a9ddf65eb7121c0af7e857295241" + "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/446fc9faa5c2a9ddf65eb7121c0af7e857295241", - "reference": "446fc9faa5c2a9ddf65eb7121c0af7e857295241", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0f1f60250fccffeaf5dda91eea1c018aed1adc2a", + "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a", "shasum": "" }, "require": { @@ -121,7 +121,7 @@ "pseudorandom", "random" ], - "time": "2020-10-15T10:06:57+00:00" + "time": "2021-04-17T09:33:01+00:00" }, { "name": "yzalis/identicon", diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index af6e497..612a1c9 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -121,19 +121,22 @@ class TrafficLimiter extends AbstractPersistence $address = \IPLib\Factory::addressFromString($_SERVER[self::$_ipKey]); $range = \IPLib\Factory::rangeFromString(trim($ipRange)); - // If $range is null something went wrong (possible invalid ip given in config). It's here becaue matches($range) does not accepts null vallue + // address could not be parsed, we might not be in IP space and try a string comparison instead + if ($address == null) { + return $_SERVER[self::$_ipKey] === $ipRange; + } + // range could not be parsed, possibly an invalid ip range given in config if ($range == null) { return false; } - // Ip-lib does throws and exception when something goes wrong, if so we want to catch it and set contained to false + // Ip-lib throws an exception when something goes wrong, if so we want to catch it and set contained to false try { return $address->matches($range); } catch (\Exception $e) { - // If something is wrong with matching the ip, we do nothing + // If something is wrong with matching the ip, we assume it doesn't match + return false; } - - return false; } /** diff --git a/tst/Persistence/TrafficLimiterTest.php b/tst/Persistence/TrafficLimiterTest.php index 7376938..4101301 100644 --- a/tst/Persistence/TrafficLimiterTest.php +++ b/tst/Persistence/TrafficLimiterTest.php @@ -35,5 +35,20 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase $this->assertTrue(TrafficLimiter::canPass(), 'fourth request has different ip and may pass'); $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; $this->assertFalse(TrafficLimiter::canPass(), 'fifth request is to fast, may not pass'); + + // exempted IPs configuration + TrafficLimiter::setExemptedIp('1.2.3.4,10.10.10.0/24,2001:1620:2057::/48'); + $this->assertFalse(TrafficLimiter::canPass(), 'still too fast and not exempted'); + $_SERVER['REMOTE_ADDR'] = '10.10.10.10'; + $this->assertTrue(TrafficLimiter::canPass(), 'IPv4 in exempted range'); + $this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv4 in exempted range'); + $_SERVER['REMOTE_ADDR'] = '2001:1620:2057:dead:beef::cafe:babe'; + $this->assertTrue(TrafficLimiter::canPass(), 'IPv6 in exempted range'); + $this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv6 in exempted range'); + TrafficLimiter::setExemptedIp('127.*,foobar'); + $this->assertFalse(TrafficLimiter::canPass(), 'request is to fast, invalid range'); + $_SERVER['REMOTE_ADDR'] = 'foobar'; + $this->assertTrue(TrafficLimiter::canPass(), 'non-IP address'); + $this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but non-IP address matches exempted range'); } } diff --git a/vendor/paragonie/random_compat/lib/random.php b/vendor/paragonie/random_compat/lib/random.php index 36245f5..6df1cb0 100644 --- a/vendor/paragonie/random_compat/lib/random.php +++ b/vendor/paragonie/random_compat/lib/random.php @@ -54,9 +54,9 @@ if (!defined('RANDOM_COMPAT_READ_BUFFER')) { $RandomCompatDIR = dirname(__FILE__); -require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'byte_safe_strings.php'; -require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'cast_to_int.php'; -require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'error_polyfill.php'; +require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'byte_safe_strings.php'; +require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'cast_to_int.php'; +require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'error_polyfill.php'; if (!is_callable('random_bytes')) { /** @@ -76,9 +76,9 @@ if (!is_callable('random_bytes')) { if (extension_loaded('libsodium')) { // See random_bytes_libsodium.php if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium.php'; + require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_libsodium.php'; } elseif (method_exists('Sodium', 'randombytes_buf')) { - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium_legacy.php'; + require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_libsodium_legacy.php'; } } @@ -117,7 +117,7 @@ if (!is_callable('random_bytes')) { // place, that is not helpful to us here. // See random_bytes_dev_urandom.php - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_dev_urandom.php'; + require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_dev_urandom.php'; } // Unset variables after use $RandomCompat_basedir = null; @@ -159,7 +159,7 @@ if (!is_callable('random_bytes')) { extension_loaded('mcrypt') ) { // See random_bytes_mcrypt.php - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_mcrypt.php'; + require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_mcrypt.php'; } $RandomCompatUrandom = null; @@ -182,9 +182,10 @@ if (!is_callable('random_bytes')) { if (!in_array('com', $RandomCompat_disabled_classes)) { try { $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); + /** @psalm-suppress TypeDoesNotContainType */ if (method_exists($RandomCompatCOMtest, 'GetRandom')) { // See random_bytes_com_dotnet.php - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_com_dotnet.php'; + require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_bytes_com_dotnet.php'; } } catch (com_exception $e) { // Don't try to use it. @@ -219,7 +220,7 @@ if (!is_callable('random_bytes')) { } if (!is_callable('random_int')) { - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_int.php'; + require_once $RandomCompatDIR.DIRECTORY_SEPARATOR.'random_int.php'; } $RandomCompatDIR = null; diff --git a/vendor/paragonie/random_compat/phpunit-autoload.php b/vendor/paragonie/random_compat/phpunit-autoload.php deleted file mode 100644 index 87b01aa..0000000 --- a/vendor/paragonie/random_compat/phpunit-autoload.php +++ /dev/null @@ -1,14 +0,0 @@ -= 5.3 - * - * Class PHPUnit_Framework_TestCase - */ -if (PHP_VERSION_ID >= 50300) { - if (!class_exists('PHPUnit_Framework_TestCase')) { - require_once __DIR__ . '/other/phpunit-shim.php'; - } -} From 84771d716713a51aadc84ab2721c01f7c21fc97e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 May 2021 11:01:16 +0200 Subject: [PATCH 096/478] documenting changes --- CHANGELOG.md | 2 ++ CREDITS.md | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e416b9..0a8ed40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ * 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) * CHANGED: Language selection cookie only transmitted over HTTPS (#472) + * CHANGED: Upgrading libraries to: random_compat 2.0.20 * **1.3.5 (2021-04-05)** * ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan * ADDED: Make the project info configurable (#681) diff --git a/CREDITS.md b/CREDITS.md index 1aabf6f..338c2df 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -27,6 +27,7 @@ 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 ## Translations * Hexalyse - French From 91c8f9f23c9a302c567b328eef73eb49540f2872 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 May 2021 11:02:54 +0200 Subject: [PATCH 097/478] use namespaces --- lib/Persistence/TrafficLimiter.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 612a1c9..dedf588 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -13,6 +13,8 @@ namespace PrivateBin\Persistence; +use Exception; +use IPLib\Factory; use PrivateBin\Configuration; /** @@ -118,8 +120,8 @@ class TrafficLimiter extends AbstractPersistence private static function matchIp($ipRange = null) { // Match $_ipKey to $ipRange and if it matches it will return with a true - $address = \IPLib\Factory::addressFromString($_SERVER[self::$_ipKey]); - $range = \IPLib\Factory::rangeFromString(trim($ipRange)); + $address = Factory::addressFromString($_SERVER[self::$_ipKey]); + $range = Factory::rangeFromString(trim($ipRange)); // address could not be parsed, we might not be in IP space and try a string comparison instead if ($address == null) { @@ -133,7 +135,7 @@ class TrafficLimiter extends AbstractPersistence // Ip-lib throws an exception when something goes wrong, if so we want to catch it and set contained to false try { return $address->matches($range); - } catch (\Exception $e) { + } catch (Exception $e) { // If something is wrong with matching the ip, we assume it doesn't match return false; } From b6460616baa2d7f0a940dffd3f5b5efb9ecd00fd Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 May 2021 11:30:17 +0200 Subject: [PATCH 098/478] address Scrutinizer issues --- lib/Persistence/TrafficLimiter.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index dedf588..b45dcf8 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -38,7 +38,7 @@ class TrafficLimiter extends AbstractPersistence * * @access private * @static - * @var array + * @var string|null */ private static $_exemptedIp = null; @@ -110,7 +110,7 @@ class TrafficLimiter extends AbstractPersistence } /** - * Validate $_ipKey against configured ipranges. If matched ratelimiter will ignore ip + * Validate $_ipKey against configured ipranges. If matched we will ignore the ip * * @access private * @static @@ -119,9 +119,11 @@ class TrafficLimiter extends AbstractPersistence */ private static function matchIp($ipRange = null) { - // Match $_ipKey to $ipRange and if it matches it will return with a true + if (is_string($ipRange)) { + $ipRange = trim($ipRange); + } $address = Factory::addressFromString($_SERVER[self::$_ipKey]); - $range = Factory::rangeFromString(trim($ipRange)); + $range = Factory::rangeFromString($ipRange); // address could not be parsed, we might not be in IP space and try a string comparison instead if ($address == null) { @@ -148,7 +150,7 @@ class TrafficLimiter extends AbstractPersistence * * @access public * @static - * @throws \Exception + * @throws Exception * @return bool */ public static function canPass() From 7de12d64d532f9cc28c8122b9e553b257bd18b75 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 May 2021 11:35:53 +0200 Subject: [PATCH 099/478] be more precise --- lib/Persistence/TrafficLimiter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index b45dcf8..be76b8c 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -126,11 +126,11 @@ class TrafficLimiter extends AbstractPersistence $range = Factory::rangeFromString($ipRange); // address could not be parsed, we might not be in IP space and try a string comparison instead - if ($address == null) { + if (is_null($address)) { return $_SERVER[self::$_ipKey] === $ipRange; } // range could not be parsed, possibly an invalid ip range given in config - if ($range == null) { + if (is_null($range)) { return false; } From dae093bbfa73fff54ae6521d81d6d5f4bbd0d226 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 May 2021 11:40:34 +0200 Subject: [PATCH 100/478] import latest scrutinizer configuration from web backend --- .gitattributes | 1 + .scrutinizer.yml | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 .scrutinizer.yml diff --git a/.gitattributes b/.gitattributes index ef06061..28b68d1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,6 +16,7 @@ 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 composer.json export-ignore diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..e3c8fe1 --- /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: '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 From 342270d6dd82e956313cb93008a1bd37576321c5 Mon Sep 17 00:00:00 2001 From: Mark van Holsteijn Date: Fri, 28 May 2021 22:39:50 +0200 Subject: [PATCH 101/478] added Google Cloud Storage support --- .gitattributes | 1 + .github/workflows/tests.yml | 4 + .gitignore | 4 +- .scrutinizer.yml | 39 ++ INSTALL.md | 12 + cfg/conf.sample.php | 7 + composer.json | 5 +- composer.lock | 132 ++++- lib/Configuration.php | 10 + lib/Data/GoogleCloudStorage.php | 251 +++++++++ lib/Persistence/TrafficLimiter.php | 4 +- tst/Data/GoogleCloudStorageTest.php | 715 ++++++++++++++++++++++++++ vendor/composer/ClassLoader.php | 40 +- vendor/composer/InstalledVersions.php | 326 ++++++++++++ vendor/composer/autoload_classmap.php | 2 + vendor/composer/autoload_real.php | 6 +- vendor/composer/autoload_static.php | 2 + vendor/composer/installed.php | 51 ++ vendor/composer/platform_check.php | 26 + 19 files changed, 1621 insertions(+), 16 deletions(-) create mode 100644 .scrutinizer.yml create mode 100644 lib/Data/GoogleCloudStorage.php create mode 100644 tst/Data/GoogleCloudStorageTest.php create mode 100644 vendor/composer/InstalledVersions.php create mode 100644 vendor/composer/installed.php create mode 100644 vendor/composer/platform_check.php diff --git a/.gitattributes b/.gitattributes index ef06061..28b68d1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,6 +16,7 @@ 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 composer.json export-ignore diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c1ac3d9..c4bfae6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,6 +11,8 @@ jobs: run: composer validate - name: Install dependencies run: /usr/bin/php7.4 $(which composer) install --prefer-dist --no-suggest + - name: Install Google Cloud Storage + run: /usr/bin/php7.4 $(which composer) require google/cloud-storage PHPunit: runs-on: ubuntu-latest strategy: @@ -29,6 +31,8 @@ jobs: run: rm composer.lock - name: Setup PHPunit run: composer install -n + - name: Install Google Cloud Storage + run: composer require google/cloud-storage - name: Run unit tests run: ../vendor/bin/phpunit --no-coverage working-directory: tst diff --git a/.gitignore b/.gitignore index a4cd2bb..65ef718 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 0000000..e6957a5 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,39 @@ +checks: + php: true + javascript: true +filter: + paths: + - "css/privatebin.css" + - "css/bootstrap/privatebin.css" + - "js/privatebin.js" + - "lib/*.php" + - "index.php" + excluded_paths: + - lib/Data/GoogleCloudStorage.php + - tst/Data/GoogleCloudStorageTest.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/INSTALL.md b/INSTALL.md index df0cac2..ec0d7f4 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -191,3 +191,15 @@ 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. + +### Using Google Cloud Storage +If you want to deploy PrivateBin in a serverless manner, 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 google/cloud-storage library. diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index e9e500b..a4b7f6b 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -167,6 +167,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/composer.json b/composer.json index a8e98aa..6f50777 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,9 @@ "yzalis/identicon" : "2.0.0", "mlocati/ip-lib" : "1.14.0" }, + "suggest": { + "google/cloud-storage": "1.23.1" + }, "require-dev" : { "phpunit/phpunit" : "^4.6 || ^5.0" }, @@ -40,4 +43,4 @@ "config" : { "autoloader-suffix" : "DontChange" } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 931c92f..96183d1 100644 --- a/composer.lock +++ b/composer.lock @@ -62,6 +62,10 @@ "range", "subnet" ], + "support": { + "issues": "https://github.com/mlocati/ip-lib/issues", + "source": "https://github.com/mlocati/ip-lib/tree/1.14.0" + }, "funding": [ { "url": "https://github.com/sponsors/mlocati", @@ -121,6 +125,11 @@ "pseudorandom", "random" ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, "time": "2021-04-17T09:33:01+00:00" }, { @@ -173,6 +182,10 @@ "identicon", "image" ], + "support": { + "issues": "https://github.com/yzalis/Identicon/issues", + "source": "https://github.com/yzalis/Identicon/tree/master" + }, "abandoned": true, "time": "2019-10-14T09:30:57+00:00" } @@ -227,6 +240,10 @@ "constructor", "instantiate" ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -289,6 +306,10 @@ "object", "object graph" ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", @@ -344,6 +365,10 @@ "reflection", "static analysis" ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, "time": "2020-06-27T09:03:43+00:00" }, { @@ -396,6 +421,10 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, "time": "2020-09-03T19:13:55+00:00" }, { @@ -441,6 +470,10 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, "time": "2020-09-17T18:55:26+00:00" }, { @@ -504,6 +537,10 @@ "spy", "stub" ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" + }, "time": "2020-03-05T15:02:03+00:00" }, { @@ -567,6 +604,11 @@ "testing", "xunit" ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/4.0" + }, "time": "2017-04-02T07:44:40+00:00" }, { @@ -614,6 +656,11 @@ "filesystem", "iterator" ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5" + }, "time": "2017-11-27T13:52:08+00:00" }, { @@ -655,6 +702,10 @@ "keywords": [ "template" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, "time": "2015-06-21T13:50:34+00:00" }, { @@ -704,6 +755,10 @@ "keywords": [ "timer" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/master" + }, "time": "2017-02-26T11:10:40+00:00" }, { @@ -753,6 +808,10 @@ "keywords": [ "tokenizer" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" + }, "abandoned": true, "time": "2017-11-27T05:48:46+00:00" }, @@ -836,6 +895,10 @@ "testing", "xunit" ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/5.7.27" + }, "time": "2018-02-01T05:50:59+00:00" }, { @@ -895,6 +958,11 @@ "mock", "xunit" ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues", + "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/3.4" + }, "abandoned": true, "time": "2017-06-30T09:13:00+00:00" }, @@ -941,6 +1009,10 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1011,6 +1083,10 @@ "compare", "equality" ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/1.2" + }, "time": "2017-01-29T09:50:25+00:00" }, { @@ -1063,6 +1139,10 @@ "keywords": [ "diff" ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/1.4" + }, "time": "2017-05-22T07:24:03+00:00" }, { @@ -1113,6 +1193,10 @@ "environment", "hhvm" ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/master" + }, "time": "2016-11-26T07:53:53+00:00" }, { @@ -1180,6 +1264,10 @@ "export", "exporter" ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/master" + }, "time": "2016-11-19T08:54:04+00:00" }, { @@ -1231,6 +1319,10 @@ "keywords": [ "global state" ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1" + }, "time": "2015-10-12T03:26:01+00:00" }, { @@ -1277,6 +1369,10 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/master" + }, "time": "2017-02-18T15:18:39+00:00" }, { @@ -1330,6 +1426,10 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/master" + }, "time": "2016-11-19T07:33:16+00:00" }, { @@ -1372,6 +1472,10 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/master" + }, "time": "2015-07-28T20:34:47+00:00" }, { @@ -1415,20 +1519,24 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/master" + }, "time": "2016-10-03T07:35:21+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { @@ -1440,7 +1548,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1477,6 +1585,9 @@ "polyfill", "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1491,7 +1602,7 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/yaml", @@ -1545,6 +1656,9 @@ ], "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v4.4.24" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1613,6 +1727,10 @@ "check", "validate" ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, "time": "2021-03-09T10:59:23+00:00" } ], @@ -1625,5 +1743,5 @@ "php": "^5.6.0 || ^7.0 || ^8.0" }, "platform-dev": [], - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } diff --git a/lib/Configuration.php b/lib/Configuration.php index 5de6de3..1e92fc9 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -153,6 +153,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/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php new file mode 100644 index 0000000..1a1d8bf --- /dev/null +++ b/lib/Data/GoogleCloudStorage.php @@ -0,0 +1,251 @@ +_client = new StorageClient(array('suppressKeyFileNotice' => true)); + } else { + // use given client for test purposes + $this->_client = $client; + } + + $this->_bucket = $this->_client->bucket($bucket); + if ($prefix != null) { + $this->_prefix = $prefix; + } + } + + /** + * returns the google storage object key for $pasteid in $this->_bucket. + * @param $pasteid string to get the key for + * @return string + */ + private function _getKey($pasteid) + { + if ($this->_prefix != '') { + return $this->_prefix . '/' . $pasteid; + } + return $pasteid; + } + + /** + * Uploads the payload in the $this->_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 { + $this->_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 ' . $this->_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 = $this->_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 ' . $this->_bucket->name() . ', ' . + trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); + return false; + } + } + + /** + * @inheritDoc + */ + public function delete($pasteid) + { + $name = $this->_getKey($pasteid); + + try { + foreach ($this->_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) { + try { + $this->_bucket->object($comment->name())->delete(); + } catch (NotFoundException $e) { + // ignore if already deleted. + } + } + } catch (NotFoundException $e) { + // there are no discussions associated with the paste + } + + try { + $this->_bucket->object($name)->delete(); + } catch (NotFoundException $e) { + // ignore if already deleted + } + } + + /** + * @inheritDoc + */ + public function exists($pasteid) + { + $o = $this->_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 ($this->_bucket->objects(array('prefix' => $prefix)) as $key) { + $comment = JSON::decode($this->_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 = $this->_bucket->object($name); + return $o->exists(); + } + + /** + * @inheritDoc + */ + protected function _getExpiredPastes($batchsize) + { + $expired = array(); + + $now = time(); + $prefix = $this->_prefix; + if ($prefix != '') { + $prefix = $prefix . '/'; + } + try { + foreach ($this->_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/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index b45dcf8..be76b8c 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -126,11 +126,11 @@ class TrafficLimiter extends AbstractPersistence $range = Factory::rangeFromString($ipRange); // address could not be parsed, we might not be in IP space and try a string comparison instead - if ($address == null) { + if (is_null($address)) { return $_SERVER[self::$_ipKey] === $ipRange; } // range could not be parsed, possibly an invalid ip range given in config - if ($range == null) { + if (is_null($range)) { return false; } diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php new file mode 100644 index 0000000..91a5ca9 --- /dev/null +++ b/tst/Data/GoogleCloudStorageTest.php @@ -0,0 +1,715 @@ +false)); + $handler = HttpHandlerFactory::build($httpClient); + + $name = 'pb-'; + $alphabet = 'abcdefghijklmnopqrstuvwxyz'; + for ($i = 0; $i < 29; ++$i) { + $name .= $alphabet[rand(0, strlen($alphabet) - 1)]; + } + self::$_client = new StorageClientStub(array()); + self::$_bucket = self::$_client->createBucket($name); + } + + public function setUp() + { + // do not report E_NOTICE as fsouza/fake-gcs-server does not return a `generation` value in the response + // which the Google Cloud Storage PHP library expects. + error_reporting(E_ERROR | E_WARNING | E_PARSE); + $this->_model = GoogleCloudStorage::getInstance(array( + 'bucket' => self::$_bucket->name(), + 'prefix' => 'pastes', + 'client' => self::$_client, )); + } + + public function tearDown() + { + foreach (self::$_bucket->objects() as $object) { + $object->delete(); + } + error_reporting(E_ALL); + } + + public static function tearDownAfterClass() + { + self::$_bucket->delete(); + } + + public function testFileBasedDataStoreWorks() + { + $this->_model->delete(Helper::getPasteId()); + + // storing pastes + $paste = Helper::getPaste(2, array('expire_date' => 1344803344)); + $this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does not yet exist'); + $this->assertTrue($this->_model->create(Helper::getPasteId(), $paste), 'store new paste'); + $this->assertTrue($this->_model->exists(Helper::getPasteId()), 'paste exists after storing it'); + $this->assertFalse($this->_model->create(Helper::getPasteId(), $paste), 'unable to store the same paste twice'); + $this->assertEquals($paste, $this->_model->read(Helper::getPasteId())); + + // storing comments + $this->assertFalse($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment does not yet exist'); + $this->assertTrue($this->_model->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), Helper::getComment()), 'store comment'); + $this->assertTrue($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment exists after storing it'); + $this->assertFalse($this->_model->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), Helper::getComment()), 'unable to store the same comment twice'); + $comment = Helper::getComment(); + $comment['id'] = Helper::getCommentId(); + $comment['parentid'] = Helper::getPasteId(); + $this->assertEquals( + array($comment['meta']['created'] => $comment), + $this->_model->readComments(Helper::getPasteId()) + ); + + // deleting pastes + $this->_model->delete(Helper::getPasteId()); + $this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste successfully deleted'); + $this->assertFalse($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment was deleted with paste'); + $this->assertFalse($this->_model->read(Helper::getPasteId()), 'paste can no longer be found'); + } + + /** + * pastes a-g are expired and should get deleted, x never expires and y-z expire in an hour + */ + public function testPurge() + { + $expired = Helper::getPaste(2, array('expire_date' => 1344803344)); + $paste = Helper::getPaste(2, array('expire_date' => time() + 3600)); + $keys = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z'); + $ids = array(); + foreach ($keys as $key) { + $ids[$key] = hash('fnv164', $key); + $this->assertFalse($this->_model->exists($ids[$key]), "paste $key does not yet exist"); + if (in_array($key, array('x', 'y', 'z'))) { + $this->assertTrue($this->_model->create($ids[$key], $paste), "store $key paste"); + } elseif ($key === 'x') { + $this->assertTrue($this->_model->create($ids[$key], Helper::getPaste()), "store $key paste"); + } else { + $this->assertTrue($this->_model->create($ids[$key], $expired), "store $key paste"); + } + $this->assertTrue($this->_model->exists($ids[$key]), "paste $key exists after storing it"); + } + $this->_model->purge(10); + foreach ($ids as $key => $id) { + if (in_array($key, array('x', 'y', 'z'))) { + $this->assertTrue($this->_model->exists($id), "paste $key exists after purge"); + $this->_model->delete($id); + } else { + $this->assertFalse($this->_model->exists($id), "paste $key was purged"); + } + } + } + + public function testErrorDetection() + { + $this->_model->delete(Helper::getPasteId()); + $paste = Helper::getPaste(2, array('expire' => "Invalid UTF-8 sequence: \xB1\x31")); + $this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does not yet exist'); + $this->assertFalse($this->_model->create(Helper::getPasteId(), $paste), 'unable to store broken paste'); + $this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does still not exist'); + } + + public function testCommentErrorDetection() + { + $this->_model->delete(Helper::getPasteId()); + $comment = Helper::getComment(1, array('nickname' => "Invalid UTF-8 sequence: \xB1\x31")); + $this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does not yet exist'); + $this->assertTrue($this->_model->create(Helper::getPasteId(), Helper::getPaste()), 'store new paste'); + $this->assertTrue($this->_model->exists(Helper::getPasteId()), 'paste exists after storing it'); + $this->assertFalse($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment does not yet exist'); + $this->assertFalse($this->_model->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), $comment), 'unable to store broken comment'); + $this->assertFalse($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment does still not exist'); + } +} + +/** + * Class StorageClientStub provides a limited stub for performing the unit test + */ +class StorageClientStub extends StorageClient +{ + private $_config = null; + private $_connection = null; + private $_buckets = array(); + + public function __construct(array $config = array()) + { + $this->_config = $config; + $this->_connection = new ConnectionInterfaceStub(); + } + + public function bucket($name, $userProject = false) + { + if (!key_exists($name, $this->_buckets)) { + $b = new BucketStub($this->_connection, $name, array(), $this); + $this->_buckets[$name] = $b; + } + return $this->_buckets[$name]; + } + + /** + * @throws \Google\Cloud\Core\Exception\NotFoundException + */ + public function deleteBucket($name) + { + if (key_exists($name, $this->_buckets)) { + unset($this->_buckets[$name]); + } else { + throw new NotFoundException(); + } + } + + public function buckets(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function registerStreamWrapper($protocol = null) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function unregisterStreamWrapper($protocol = null) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function signedUrlUploader($uri, $data, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function timestamp(\DateTimeInterface $timestamp, $nanoSeconds = null) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getServiceAccount(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function hmacKeys(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function hmacKey($accessId, $projectId = null, array $metadata = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function createHmacKey($serviceAccountEmail, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function createBucket($name, array $options = array()) + { + if (key_exists($name, $this->_buckets)) { + throw new BadRequestException('already exists'); + } + $b = new BucketStub($this->_connection, $name, array(), $this); + $this->_buckets[$name] = $b; + return $b; + } +} + +/** + * Class BucketStub stubs a GCS bucket. + */ +class BucketStub extends Bucket +{ + public $_objects; + private $_name; + private $_info; + private $_connection; + private $_client; + + public function __construct(ConnectionInterface $connection, $name, array $info = array(), $client = null) + { + $this->_name = $name; + $this->_info = $info; + $this->_connection = $connection; + $this->_objects = array(); + $this->_client = $client; + } + + public function acl() + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function defaultAcl() + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function exists() + { + return true; + } + + public function upload($data, array $options = array()) + { + if (!is_string($data) || !key_exists('name', $options)) { + throw new BadMethodCallException('not supported by this stub'); + } + + $name = $options['name']; + $generation = '1'; + $o = new StorageObjectStub($this->_connection, $name, $this, $generation, $options); + $this->_objects[$options['name']] = $o; + $o->setData($data); + } + + public function uploadAsync($data, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getResumableUploader($data, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getStreamableUploader($data, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function object($name, array $options = array()) + { + if (key_exists($name, $this->_objects)) { + return $this->_objects[$name]; + } else { + return new StorageObjectStub($this->_connection, $name, $this, null, $options); + } + } + + public function objects(array $options = array()) + { + $prefix = key_exists('prefix', $options) ? $options['prefix'] : ''; + + return new CallbackFilterIterator( + new ArrayIterator($this->_objects), + function ($current, $key, $iterator) use ($prefix) { + return substr($key, 0, strlen($prefix)) == $prefix; + } + ); + } + + public function createNotification($topic, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function notification($id) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function notifications(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function delete(array $options = array()) + { + $this->_client->deleteBucket($this->_name); + } + + public function update(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function compose(array $sourceObjects, $name, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function info(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function reload(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function name() + { + return $this->_name; + } + + public static function lifecycle(array $lifecycle = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function currentLifecycle(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function isWritable($file = null) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function iam() + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function lockRetentionPolicy(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function signedUrl($expires, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function generateSignedPostPolicyV4($objectName, $expires, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } +} + +/** + * Class StorageObjectStub stubs a GCS storage object. + */ +class StorageObjectStub extends StorageObject +{ + private $_name; + private $_data; + private $_info; + private $_bucket; + private $_generation; + private $_exists = false; + private $_connection; + + public function __construct(ConnectionInterface $connection, $name, $bucket, $generation = null, array $info = array(), $encryptionKey = null, $encryptionKeySHA256 = null) + { + $this->_name = $name; + $this->_bucket = $bucket; + $this->_generation = $generation; + $this->_info = $info; + $this->_connection = $connection; + } + + public function acl() + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function exists(array $options = array()) + { + return key_exists($this->_name, $this->_bucket->_objects); + } + + /** + * @throws NotFoundException + */ + public function delete(array $options = array()) + { + if (key_exists($this->_name, $this->_bucket->_objects)) { + unset($this->_bucket->_objects[$this->_name]); + } else { + throw new NotFoundException('key ' . $this->_name . ' not found.'); + } + } + + /** + * @throws NotFoundException + */ + public function update(array $metadata, array $options = array()) + { + if (!$this->_exists) { + throw new NotFoundException('key ' . $this->_name . ' not found.'); + } + $this->_info = $metadata; + } + + public function copy($destination, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function rewrite($destination, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function rename($name, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + /** + * @throws NotFoundException + */ + public function downloadAsString(array $options = array()) + { + if (!$this->_exists) { + throw new NotFoundException('key ' . $this->_name . ' not found.'); + } + return $this->_data; + } + + public function downloadToFile($path, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function downloadAsStream(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function downloadAsStreamAsync(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function signedUrl($expires, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function signedUploadUrl($expires, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function beginSignedUploadSession(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function info(array $options = array()) + { + return key_exists('metadata',$this->_info) ? $this->_info['metadata'] : array(); + } + + public function reload(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function name() + { + return $this->_name; + } + + public function identity() + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function gcsUri() + { + return sprintf( + 'gs://%s/%s', + $this->_bucket->name(), + $this->_name + ); + } + + public function setData($data) + { + $this->_data = $data; + $this->_exists = true; + } +} + +/** + * Class ConnectionInterfaceStub required for the stubs. + */ +class ConnectionInterfaceStub implements ConnectionInterface +{ + public function deleteAcl(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getAcl(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function listAcl(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function insertAcl(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function patchAcl(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function deleteBucket(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getBucket(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function listBuckets(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function insertBucket(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getBucketIamPolicy(array $args) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function setBucketIamPolicy(array $args) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function testBucketIamPermissions(array $args) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function patchBucket(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function deleteObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function copyObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function rewriteObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function composeObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function listObjects(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function patchObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function downloadObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function insertObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getNotification(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function deleteNotification(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function insertNotification(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function listNotifications(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getServiceAccount(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function lockRetentionPolicy(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function createHmacKey(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function deleteHmacKey(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getHmacKey(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function updateHmacKey(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function listHmacKeys(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } +} diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index fce8549..247294d 100644 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -37,11 +37,13 @@ namespace Composer\Autoload; * * @author Fabien Potencier * @author Jordi Boggiano - * @see http://www.php-fig.org/psr/psr-0/ - * @see http://www.php-fig.org/psr/psr-4/ + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { + private $vendorDir; + // PSR-4 private $prefixLengthsPsr4 = array(); private $prefixDirsPsr4 = array(); @@ -57,10 +59,17 @@ class ClassLoader private $missingClasses = array(); private $apcuPrefix; + private static $registeredLoaders = array(); + + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + } + public function getPrefixes() { if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', $this->prefixesPsr0); + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); @@ -300,6 +309,17 @@ class ClassLoader public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } } /** @@ -308,6 +328,10 @@ class ClassLoader public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } } /** @@ -367,6 +391,16 @@ class ClassLoader return $file; } + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + private function findFileWithExtension($class, $ext) { // PSR-4 lookup diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..9766d9b --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,326 @@ + + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => 'b6460616baa2d7f0a940dffd3f5b5efb9ecd00fd', + 'name' => 'privatebin/privatebin', + ), + 'versions' => + array ( + 'mlocati/ip-lib' => + array ( + 'pretty_version' => '1.14.0', + 'version' => '1.14.0.0', + 'aliases' => + array ( + ), + 'reference' => '882bc0e115970a536b13bcfa59f312783fce08c8', + ), + 'paragonie/random_compat' => + array ( + 'pretty_version' => 'v2.0.20', + 'version' => '2.0.20.0', + 'aliases' => + array ( + ), + 'reference' => '0f1f60250fccffeaf5dda91eea1c018aed1adc2a', + ), + 'privatebin/privatebin' => + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => 'b6460616baa2d7f0a940dffd3f5b5efb9ecd00fd', + ), + 'yzalis/identicon' => + array ( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'ff5ed090129cab9bfa2a322857d4a01d107aa0ae', + ), + ), +); +private static $canGetVendors; +private static $installedByVendor = array(); + + + + + + + +public static function getInstalledPackages() +{ +$packages = array(); +foreach (self::getInstalled() as $installed) { +$packages[] = array_keys($installed['versions']); +} + +if (1 === \count($packages)) { +return $packages[0]; +} + +return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); +} + + + + + + + + + +public static function isInstalled($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (isset($installed['versions'][$packageName])) { +return true; +} +} + +return false; +} + + + + + + + + + + + + + + +public static function satisfies(VersionParser $parser, $packageName, $constraint) +{ +$constraint = $parser->parseConstraints($constraint); +$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + +return $provided->matches($constraint); +} + + + + + + + + + + +public static function getVersionRanges($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +$ranges = array(); +if (isset($installed['versions'][$packageName]['pretty_version'])) { +$ranges[] = $installed['versions'][$packageName]['pretty_version']; +} +if (array_key_exists('aliases', $installed['versions'][$packageName])) { +$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); +} +if (array_key_exists('replaced', $installed['versions'][$packageName])) { +$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); +} +if (array_key_exists('provided', $installed['versions'][$packageName])) { +$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); +} + +return implode(' || ', $ranges); +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getVersion($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +if (!isset($installed['versions'][$packageName]['version'])) { +return null; +} + +return $installed['versions'][$packageName]['version']; +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getPrettyVersion($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +if (!isset($installed['versions'][$packageName]['pretty_version'])) { +return null; +} + +return $installed['versions'][$packageName]['pretty_version']; +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getReference($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +if (!isset($installed['versions'][$packageName]['reference'])) { +return null; +} + +return $installed['versions'][$packageName]['reference']; +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getRootPackage() +{ +$installed = self::getInstalled(); + +return $installed[0]['root']; +} + + + + + + + + +public static function getRawData() +{ +@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + +return self::$installed; +} + + + + + + + +public static function getAllRawData() +{ +return self::getInstalled(); +} + + + + + + + + + + + + + + + + + + + +public static function reload($data) +{ +self::$installed = $data; +self::$installedByVendor = array(); +} + + + + + +private static function getInstalled() +{ +if (null === self::$canGetVendors) { +self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); +} + +$installed = array(); + +if (self::$canGetVendors) { +foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { +if (isset(self::$installedByVendor[$vendorDir])) { +$installed[] = self::$installedByVendor[$vendorDir]; +} elseif (is_file($vendorDir.'/composer/installed.php')) { +$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; +} +} +} + +$installed[] = self::$installed; + +return $installed; +} +} diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 81358f5..2abf65e 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'IPLib\\Address\\AddressInterface' => $vendorDir . '/mlocati/ip-lib/src/Address/AddressInterface.php', 'IPLib\\Address\\AssignedRange' => $vendorDir . '/mlocati/ip-lib/src/Address/AssignedRange.php', 'IPLib\\Address\\IPv4' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv4.php', @@ -31,6 +32,7 @@ return array( 'PrivateBin\\Data\\AbstractData' => $baseDir . '/lib/Data/AbstractData.php', 'PrivateBin\\Data\\Database' => $baseDir . '/lib/Data/Database.php', 'PrivateBin\\Data\\Filesystem' => $baseDir . '/lib/Data/Filesystem.php', + 'PrivateBin\\Data\\GoogleCloudStorage' => $baseDir . '/lib/Data/GoogleCloudStorage.php', 'PrivateBin\\Filter' => $baseDir . '/lib/Filter.php', 'PrivateBin\\FormatV2' => $baseDir . '/lib/FormatV2.php', 'PrivateBin\\I18n' => $baseDir . '/lib/I18n.php', diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 7a6fd4c..fe68b5c 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -22,13 +22,15 @@ class ComposerAutoloaderInitDontChange return self::$loader; } + require __DIR__ . '/platform_check.php'; + spl_autoload_register(array('ComposerAutoloaderInitDontChange', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInitDontChange', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { - require_once __DIR__ . '/autoload_static.php'; + require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitDontChange::getInitializer($loader)); } else { diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 9197c94..cfe595d 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -38,6 +38,7 @@ class ComposerStaticInitDontChange ); public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'IPLib\\Address\\AddressInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AddressInterface.php', 'IPLib\\Address\\AssignedRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AssignedRange.php', 'IPLib\\Address\\IPv4' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv4.php', @@ -63,6 +64,7 @@ class ComposerStaticInitDontChange 'PrivateBin\\Data\\AbstractData' => __DIR__ . '/../..' . '/lib/Data/AbstractData.php', 'PrivateBin\\Data\\Database' => __DIR__ . '/../..' . '/lib/Data/Database.php', 'PrivateBin\\Data\\Filesystem' => __DIR__ . '/../..' . '/lib/Data/Filesystem.php', + 'PrivateBin\\Data\\GoogleCloudStorage' => __DIR__ . '/../..' . '/lib/Data/GoogleCloudStorage.php', 'PrivateBin\\Filter' => __DIR__ . '/../..' . '/lib/Filter.php', 'PrivateBin\\FormatV2' => __DIR__ . '/../..' . '/lib/FormatV2.php', 'PrivateBin\\I18n' => __DIR__ . '/../..' . '/lib/I18n.php', diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..56f2a5e --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,51 @@ + + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => 'b6460616baa2d7f0a940dffd3f5b5efb9ecd00fd', + 'name' => 'privatebin/privatebin', + ), + 'versions' => + array ( + 'mlocati/ip-lib' => + array ( + 'pretty_version' => '1.14.0', + 'version' => '1.14.0.0', + 'aliases' => + array ( + ), + 'reference' => '882bc0e115970a536b13bcfa59f312783fce08c8', + ), + 'paragonie/random_compat' => + array ( + 'pretty_version' => 'v2.0.20', + 'version' => '2.0.20.0', + 'aliases' => + array ( + ), + 'reference' => '0f1f60250fccffeaf5dda91eea1c018aed1adc2a', + ), + 'privatebin/privatebin' => + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => 'b6460616baa2d7f0a940dffd3f5b5efb9ecd00fd', + ), + 'yzalis/identicon' => + array ( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'ff5ed090129cab9bfa2a322857d4a01d107aa0ae', + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..8b379f4 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 50600)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} From 5c5ae967a862282db12fa5e11bfb6958c7a1c171 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sat, 29 May 2021 03:56:04 +0200 Subject: [PATCH 102/478] New translations en.json (Catalan) --- i18n/ca.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/ca.json b/i18n/ca.json index a6936be..89cad69 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -8,9 +8,9 @@ "%s requires php %s or above to work. Sorry.": "%s requereix php %s o superior per funcionar. Ho sento.", "%s requires configuration section [%s] to be present in configuration file.": "%s requereix que la secció de configuració [%s] sigui present al fitxer de configuració.", "Please wait %d seconds between each post.": [ - "Please wait %d second between each post. (singular)", - "Please wait %d seconds between each post. (1st plural)", - "Please wait %d seconds between each post. (2nd plural)", + "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. (3rd plural)" ], "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", From de4bada695d8bb8b79be582b26839e7f7c124515 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sat, 29 May 2021 04:56:21 +0200 Subject: [PATCH 103/478] New translations en.json (Catalan) --- i18n/ca.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/i18n/ca.json b/i18n/ca.json index 89cad69..b2b86fe 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -11,14 +11,14 @@ "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. (3rd plural)" + "Please wait %d seconds between each post. (3er plural)" ], - "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", - "Invalid data.": "Invalid data.", - "You are unlucky. Try again.": "You are unlucky. Try again.", - "Error saving comment. Sorry.": "Error saving comment. Sorry.", - "Error saving paste. Sorry.": "Error saving paste. Sorry.", - "Invalid paste ID.": "Invalid paste ID.", + "Paste is limited to %s of encrypted data.": "L'enganxat està limitat a %s de dades encriptades.", + "Invalid data.": "Dades no vàlides.", + "You are unlucky. Try again.": "Mala sort. Torna-ho a provar.", + "Error saving comment. Sorry.": "S'ha produït un error en desar el comentari. Ho sento.", + "Error saving paste. Sorry.": "S'ha produït un error en desar l'enganxat. Ho sento.", + "Invalid paste ID.": "Identificador d'enganxament no vàlid.", "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", "Paste was properly deleted.": "Paste was properly deleted.", From d355bb87e361787beb3258c9da5bc906ebc774db Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 30 May 2021 08:11:45 +0200 Subject: [PATCH 104/478] documenting changes --- CHANGELOG.md | 3 ++- CREDITS.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a8ed40..84f630e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ * 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: 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 * **1.3.5 (2021-04-05)** diff --git a/CREDITS.md b/CREDITS.md index 338c2df..612749c 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 @@ -28,6 +28,7 @@ Sébastien Sauvage - original idea and main developer * 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 From 33587d54e4b56738c543487a999d8b6d27dc61ed Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 30 May 2021 09:17:23 +0200 Subject: [PATCH 105/478] fix composer test on PHP 8 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0d1e354..a8c94ae 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: - name: Validate composer.json and composer.lock run: composer validate - name: Install dependencies - run: composer install --prefer-dist --no-suggest + run: composer install --prefer-dist --no-dev - name: Install Google Cloud Storage run: composer require google/cloud-storage PHPunit: From fc5e380ccc249a619f60ad182a17c55a49140c42 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 30 May 2021 09:18:56 +0200 Subject: [PATCH 106/478] fix composer test on PHP 8 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a8c94ae..aff27df 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: - name: Install dependencies run: composer install --prefer-dist --no-dev - name: Install Google Cloud Storage - run: composer require google/cloud-storage + run: composer require google/cloud-storage --no-dev PHPunit: runs-on: ubuntu-latest strategy: From 93138cbbae6d1ada37f3dcb5cba6a4b77a71d0f4 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 30 May 2021 09:26:13 +0200 Subject: [PATCH 107/478] we already test this via the regular unit tests --- .github/workflows/tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aff27df..73fa11a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,8 +11,6 @@ jobs: run: composer validate - name: Install dependencies run: composer install --prefer-dist --no-dev - - name: Install Google Cloud Storage - run: composer require google/cloud-storage --no-dev PHPunit: runs-on: ubuntu-latest strategy: From 3df6b62d222d0073d80f3251038a522cad103888 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 3 Jun 2021 03:00:55 +0200 Subject: [PATCH 108/478] New translations en.json (Catalan) --- i18n/ca.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/i18n/ca.json b/i18n/ca.json index b2b86fe..ef6b348 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -22,18 +22,18 @@ "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", "Paste was properly deleted.": "Paste was properly deleted.", - "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", - "%s requires a modern browser to work.": "%s requires a modern browser to work.", - "New": "New", - "Send": "Send", - "Clone": "Clone", - "Raw text": "Raw text", - "Expires": "Expires", - "Burn after reading": "Burn after reading", - "Open discussion": "Open discussion", - "Password (recommended)": "Password (recommended)", - "Discussion": "Discussion", - "Toggle navigation": "Toggle navigation", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "Cal JavaScript perquè %s funcioni. Em sap greu les molèsties.", + "%s requires a modern browser to work.": "%s requereix un navegador modern per funcionar.", + "New": "Nou", + "Send": "Enviar", + "Clone": "Clona", + "Raw text": "Text sense processar", + "Expires": "Caducitat", + "Burn after reading": "Esborra després de ser llegit", + "Open discussion": "Discussió oberta", + "Password (recommended)": "Contrasenya (recomanat)", + "Discussion": "Discussió", + "Toggle navigation": "Alternar navegació", "%d seconds": [ "%d second (singular)", "%d seconds (1st plural)", From 8bc97517fb16499b0cd20a14cd01ad286349ded8 Mon Sep 17 00:00:00 2001 From: rugk Date: Fri, 4 Jun 2021 23:43:01 +0200 Subject: [PATCH 109/478] Add Snyk security scan for PHP After I found https://github.com/PrivateBin/docker-nginx-fpm-alpine/pull/44 I saw they also support PHP, so let's do it here (one level before container packaging), too. Also it complements the CodeQL analysis, which only covers the JS part. I added the API token to the PrivateBIn org now. --- .github/workflows/snyk-scan.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/snyk-scan.yml diff --git a/.github/workflows/snyk-scan.yml b/.github/workflows/snyk-scan.yml new file mode 100644 index 0000000..af0afbd --- /dev/null +++ b/.github/workflows/snyk-scan.yml @@ -0,0 +1,27 @@ +# 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: 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 From ffe48092fecde86474a194b05c5611edfd5fa593 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Jun 2021 05:38:05 +0200 Subject: [PATCH 110/478] suppress error_log output of GoogleCloudStorage class in unit testing --- tst/Data/GoogleCloudStorageTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 91a5ca9..6905f04 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -34,6 +34,7 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase // do not report E_NOTICE as fsouza/fake-gcs-server does not return a `generation` value in the response // which the Google Cloud Storage PHP library expects. error_reporting(E_ERROR | E_WARNING | E_PARSE); + ini_set('error_log', stream_get_meta_data(tmpfile())['uri']); $this->_model = GoogleCloudStorage::getInstance(array( 'bucket' => self::$_bucket->name(), 'prefix' => 'pastes', From edb8e5e078f2557716a17c8e92f91eb657d3b728 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Jun 2021 05:48:17 +0200 Subject: [PATCH 111/478] handle edge cases with file locking: file needs to exist before it can be locked, fixes #803 --- lib/Persistence/AbstractPersistence.php | 28 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/Persistence/AbstractPersistence.php b/lib/Persistence/AbstractPersistence.php index 0dcef50..c96a0de 100644 --- a/lib/Persistence/AbstractPersistence.php +++ b/lib/Persistence/AbstractPersistence.php @@ -90,12 +90,15 @@ abstract class AbstractPersistence } $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) { + $writtenBytes = 0; + if ($fileCreated = @touch($file)) { + $writtenBytes = @file_put_contents( + $file, + 'Require all denied' . PHP_EOL, + LOCK_EX + ); + } + if ($fileCreated === false || $writtenBytes === false || $writtenBytes < 19) { throw new Exception('unable to write to file ' . $file, 11); } } @@ -114,9 +117,16 @@ abstract class AbstractPersistence 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)) { + $file = self::$_path . DIRECTORY_SEPARATOR . $filename; + $fileCreated = true; + $writtenBytes = 0; + if (!is_file($file)) { + $fileCreated = @touch($file); + } + if ($fileCreated) { + $writtenBytes = @file_put_contents($file, $data, LOCK_EX); + } + if ($fileCreated === false || $writtenBytes === false || $writtenBytes < strlen($data)) { throw new Exception('unable to write to file ' . $file, 13); } @chmod($file, 0640); // protect file access From abb2b90e9b13e09ee8853a6ff20f5617a4aadd94 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Jun 2021 05:52:13 +0200 Subject: [PATCH 112/478] make StyleCI happy --- lib/Persistence/AbstractPersistence.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Persistence/AbstractPersistence.php b/lib/Persistence/AbstractPersistence.php index c96a0de..489836d 100644 --- a/lib/Persistence/AbstractPersistence.php +++ b/lib/Persistence/AbstractPersistence.php @@ -117,8 +117,8 @@ abstract class AbstractPersistence protected static function _store($filename, $data) { self::_initialize(); - $file = self::$_path . DIRECTORY_SEPARATOR . $filename; - $fileCreated = true; + $file = self::$_path . DIRECTORY_SEPARATOR . $filename; + $fileCreated = true; $writtenBytes = 0; if (!is_file($file)) { $fileCreated = @touch($file); From 371dca19868464f3adf6a52a54423bd4f3652125 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Jun 2021 08:10:12 +0200 Subject: [PATCH 113/478] ensure the GCS library and dependencies get included in the scan --- .github/workflows/snyk-scan.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/snyk-scan.yml b/.github/workflows/snyk-scan.yml index af0afbd..15e2bee 100644 --- a/.github/workflows/snyk-scan.yml +++ b/.github/workflows/snyk-scan.yml @@ -14,6 +14,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@master + - name: Install Google Cloud Storage + run: composer require google/cloud-storage - name: Run Snyk to check for vulnerabilities uses: snyk/actions/php@master continue-on-error: true # To make sure that SARIF upload gets called From cbdcaf4c307b9894fae4a995d98924b3103a0623 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Jun 2021 08:14:04 +0200 Subject: [PATCH 114/478] fix snyk --- .github/workflows/snyk-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk-scan.yml b/.github/workflows/snyk-scan.yml index 15e2bee..3d3e3e7 100644 --- a/.github/workflows/snyk-scan.yml +++ b/.github/workflows/snyk-scan.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@master - name: Install Google Cloud Storage - run: composer require google/cloud-storage + run: composer require --no-dev google/cloud-storage - name: Run Snyk to check for vulnerabilities uses: snyk/actions/php@master continue-on-error: true # To make sure that SARIF upload gets called From 7a3a306ddcbf0cef045444b7ecd7aaab671a5f0d Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Jun 2021 08:22:50 +0200 Subject: [PATCH 115/478] fix snyk --- .github/workflows/snyk-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk-scan.yml b/.github/workflows/snyk-scan.yml index 3d3e3e7..91ae092 100644 --- a/.github/workflows/snyk-scan.yml +++ b/.github/workflows/snyk-scan.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@master - name: Install Google Cloud Storage - run: composer require --no-dev google/cloud-storage + run: composer install --no-dev google/cloud-storage - name: Run Snyk to check for vulnerabilities uses: snyk/actions/php@master continue-on-error: true # To make sure that SARIF upload gets called From 197c4a34e8a0e303cc46743834b65634cef27a3a Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Jun 2021 08:25:19 +0200 Subject: [PATCH 116/478] fix snyk --- .github/workflows/snyk-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk-scan.yml b/.github/workflows/snyk-scan.yml index 91ae092..4730eec 100644 --- a/.github/workflows/snyk-scan.yml +++ b/.github/workflows/snyk-scan.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@master - name: Install Google Cloud Storage - run: composer install --no-dev 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 From a2ffbafa136fb8db83e956c2a63f4974f9f6103f Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Jun 2021 09:43:01 +0200 Subject: [PATCH 117/478] ensure npm's package.json version gets incremented --- Makefile | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 691d72b..83a6b6d 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CURRENT_VERSION = 1.3.5 VERSION ?= 1.3.6 -VERSION_FILES = index.php cfg/ *.md css/ i18n/ img/ js/privatebin.js lib/ Makefile tpl/ tst/ +VERSION_FILES = index.php cfg/ *.md css/ i18n/ img/ js/package.json js/privatebin.js lib/ Makefile tpl/ tst/ REGEX_CURRENT_VERSION := $(shell echo $(CURRENT_VERSION) | sed "s/\./\\\./g") REGEX_VERSION := $(shell echo $(VERSION) | sed "s/\./\\\./g") diff --git a/js/package.json b/js/package.json index 489cc67..53e85dc 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "privatebin", - "version": "1.3.0", + "version": "1.3.5", "description": "PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted in the browser using 256 bit AES in Galois Counter mode (GCM).", "main": "privatebin.js", "directories": { From 2bc54caa071f35bf2e2f68dbe5df579fc2f8f59a Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Jun 2021 10:33:01 +0200 Subject: [PATCH 118/478] fix never matched condition, kudos @ShiftLeftSecurity, found via #807 --- lib/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Request.php b/lib/Request.php index cfa883a..5776cab 100644 --- a/lib/Request.php +++ b/lib/Request.php @@ -288,7 +288,7 @@ class Request } krsort($mediaTypes); foreach ($mediaTypes as $acceptedQuality => $acceptedValues) { - if ($acceptedQuality === 0.0) { + if ($acceptedQuality === '0.0') { continue; } foreach ($acceptedValues as $acceptedValue) { From 9beb17687400c08e4fb297b3cce7c1efac4acf10 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Jun 2021 17:52:10 +0200 Subject: [PATCH 119/478] these don't belong in a release --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 28b68d1..60629c0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,6 +19,8 @@ js/test/ 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 From c758eca0a4c450aa62e54614d6510d02bf638517 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Jun 2021 17:53:08 +0200 Subject: [PATCH 120/478] removed automatic .ini configuration file migration, closes #808 --- CHANGELOG.md | 1 + lib/Configuration.php | 12 ------- lib/Persistence/DataStore.php | 5 ++- tst/ConfigurationTest.php | 63 ----------------------------------- 4 files changed, 3 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84f630e..2c4321a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * 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) * **1.3.5 (2021-04-05)** * ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan * ADDED: Make the project info configurable (#681) diff --git a/lib/Configuration.php b/lib/Configuration.php index 1e92fc9..a56217b 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -106,20 +106,8 @@ class Configuration { $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', ';'); - } - } - if (is_readable($configFile)) { $config = parse_ini_file($configFile, true); foreach (array('main', 'model', 'model_options') as $section) { diff --git a/lib/Persistence/DataStore.php b/lib/Persistence/DataStore.php index d96f070..d17e5fa 100644 --- a/lib/Persistence/DataStore.php +++ b/lib/Persistence/DataStore.php @@ -80,15 +80,14 @@ class DataStore extends AbstractPersistence * @static * @param string $srcFile * @param string $destFile - * @param string $prefix (optional) * @return void */ - public static function prependRename($srcFile, $destFile, $prefix = '') + public 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, $prefix . self::PROTECTION_LINE . PHP_EOL); + file_put_contents($destFile, self::PROTECTION_LINE . PHP_EOL); file_put_contents($destFile, $handle, FILE_APPEND); fclose($handle); } diff --git a/tst/ConfigurationTest.php b/tst/ConfigurationTest.php index bb4ce36..246618c 100644 --- a/tst/ConfigurationTest.php +++ b/tst/ConfigurationTest.php @@ -147,44 +147,6 @@ class ConfigurationTest extends PHPUnit_Framework_TestCase $this->assertEquals('Database', $conf->getKey('class', 'model'), 'old db class gets renamed'); } - public function testHandleConfigFileRename() - { - $options = $this->_options; - Helper::createIniFile(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini.sample', $options); - - $options['main']['opendiscussion'] = true; - $options['main']['fileupload'] = true; - $options['main']['template'] = 'darkstrap'; - Helper::createIniFile(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', $options); - - $conf = new Configuration; - $this->assertFileExists(CONF, 'old configuration file gets converted'); - $this->assertFileNotExists(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', 'old configuration file gets removed'); - $this->assertFileNotExists(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini.sample', 'old configuration sample file gets removed'); - $this->assertTrue( - $conf->getKey('opendiscussion') && - $conf->getKey('fileupload') && - $conf->getKey('template') === 'darkstrap', - 'configuration values get converted' - ); - } - - public function testRenameIniSample() - { - $iniSample = PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini.sample'; - - Helper::createIniFile(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', $this->_options); - if (is_file(CONF)) { - unlink(CONF); - } - rename(CONF_SAMPLE, $iniSample); - new Configuration; - $this->assertFileNotExists($iniSample, 'old sample file gets removed'); - $this->assertFileExists(CONF_SAMPLE, 'new sample file gets created'); - $this->assertFileExists(CONF, 'old configuration file gets converted'); - $this->assertFileNotExists(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', 'old configuration file gets removed'); - } - public function testConfigPath() { // setup @@ -204,29 +166,4 @@ class ConfigurationTest extends PHPUnit_Framework_TestCase } putenv('CONFIG_PATH'); } - - public function testConfigPathIni() - { - // setup - $configFile = $this->_path . DIRECTORY_SEPARATOR . 'conf.ini'; - $configMigrated = $this->_path . DIRECTORY_SEPARATOR . 'conf.php'; - $options = $this->_options; - $options['main']['name'] = 'OtherBin'; - Helper::createIniFile($configFile, $options); - $this->assertFileNotExists(CONF, 'configuration in the default location is non existing'); - - // test - putenv('CONFIG_PATH=' . $this->_path); - $conf = new Configuration; - $this->assertEquals('OtherBin', $conf->getKey('name'), 'changing config path is supported for ini files as well'); - $this->assertFileExists($configMigrated, 'old configuration file gets converted'); - $this->assertFileNotExists($configFile, 'old configuration file gets removed'); - $this->assertFileNotExists(CONF, 'configuration is not created in the default location'); - - // cleanup environment - if (is_file($configFile)) { - unlink($configFile); - } - putenv('CONFIG_PATH'); - } } From de8f40ac1a8b43fe6aa6a7a2fb1ab370ee62b438 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Jun 2021 19:35:31 +0200 Subject: [PATCH 121/478] kudos @StyleCI --- lib/Configuration.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Configuration.php b/lib/Configuration.php index a56217b..1185440 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -14,7 +14,6 @@ namespace PrivateBin; use Exception; use PDO; -use PrivateBin\Persistence\DataStore; /** * Configuration From 1a7d0799c00de0e2944e0e15e29c64e64d89e4a3 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 7 Jun 2021 06:53:15 +0200 Subject: [PATCH 122/478] scaffolding interface for AbstractData key/value storage, folding Persistance\DataStore into Data\Filesystem --- lib/Data/AbstractData.php | 35 ++++++- lib/Data/Filesystem.php | 170 ++++++++++++++++++++++++++++++---- lib/Persistence/DataStore.php | 96 ------------------- 3 files changed, 185 insertions(+), 116 deletions(-) delete mode 100644 lib/Persistence/DataStore.php diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index 077864e..8a52d1d 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -20,7 +20,7 @@ namespace PrivateBin\Data; abstract class AbstractData { /** - * singleton instance + * Singleton instance * * @access protected * @static @@ -28,8 +28,14 @@ abstract class AbstractData */ protected static $_instance = null; + protected static $_namespaces = array( + 'purge_limiter', + 'salt', + 'traffic_limiter', + ); + /** - * enforce singleton, disable constructor + * Enforce singleton, disable constructor * * Instantiate using {@link getInstance()}, privatebin is a singleton object. * @@ -40,7 +46,7 @@ abstract class AbstractData } /** - * enforce singleton, disable cloning + * Enforce singleton, disable cloning * * Instantiate using {@link getInstance()}, privatebin is a singleton object. * @@ -51,7 +57,7 @@ abstract class AbstractData } /** - * get instance of singleton + * Get instance of singleton * * @access public * @static @@ -130,6 +136,27 @@ abstract class AbstractData */ abstract public function existsComment($pasteid, $parentid, $commentid); + /** + * 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/Filesystem.php b/lib/Data/Filesystem.php index 96ee691..fee61c1 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -12,7 +12,8 @@ namespace PrivateBin\Data; -use PrivateBin\Persistence\DataStore; +use Exception; +use PrivateBin\Json; /** * Filesystem @@ -21,6 +22,22 @@ use PrivateBin\Persistence\DataStore; */ class Filesystem extends AbstractData { + /** + * first line in file, to protect its contents + * + * @const string + */ + const PROTECTION_LINE = '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 +182,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 +204,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 +240,33 @@ 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 = '') + { + + } + + /** + * Load a value. + * + * @access public + * @param string $namespace + * @param string $key + * @return string + */ + public function getValue($namespace, $key = '') + { + + } + /** * Returns up to batch size number of paste ids that have expired * @@ -233,9 +277,8 @@ class Filesystem extends AbstractData protected function _getExpiredPastes($batchsize) { $pastes = array(); - $mainpath = DataStore::getPath(); $firstLevel = array_filter( - scandir($mainpath), + scandir(self::$_path), 'self::_isFirstLevelDir' ); if (count($firstLevel) > 0) { @@ -243,7 +286,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 +297,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 +357,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 +389,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 +404,100 @@ 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, $data) + { + if (strpos($filename, self::$_path) === 0) { + $filename = substr($filename, strlen(self::$_path)); + } + + // 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 = 0; + if ($fileCreated = @touch($file)) { + $writtenBytes = @file_put_contents( + $file, + 'Require all denied' . PHP_EOL, + LOCK_EX + ); + } + if ($fileCreated === false || $writtenBytes === false || $writtenBytes < 19) { + return false; + } + } + + try { + $data = self::PROTECTION_LINE . PHP_EOL . Json::encode($data); + } catch (Exception $e) { + return false; + } + $file = self::$_path . DIRECTORY_SEPARATOR . $filename; + $fileCreated = true; + $writtenBytes = 0; + if (!is_file($file)) { + $fileCreated = @touch($file); + } + if ($fileCreated) { + $writtenBytes = @file_put_contents($file, $data, LOCK_EX); + } + if ($fileCreated === false || $writtenBytes === false || $writtenBytes < strlen($data)) { + return false; + } + @chmod($file, 0640); // protect file access + return true; + } + + /** + * get the data + * + * @access public + * @static + * @param string $filename + * @return array|false $data + */ + private static function _get($filename) + { + return Json::decode( + substr( + file_get_contents($filename), + strlen(self::PROTECTION_LINE . PHP_EOL) + ) + ); + } + + /** + * 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/Persistence/DataStore.php b/lib/Persistence/DataStore.php deleted file mode 100644 index d17e5fa..0000000 --- a/lib/Persistence/DataStore.php +++ /dev/null @@ -1,96 +0,0 @@ - Date: Mon, 7 Jun 2021 07:02:47 +0200 Subject: [PATCH 123/478] conclude scaffolding of AbstractData key/value storage, missing implementation --- lib/Data/AbstractData.php | 6 ----- lib/Data/Database.php | 40 +++++++++++++++++++++++++++++++++ lib/Data/Filesystem.php | 15 ++++++++++++- lib/Data/GoogleCloudStorage.php | 40 +++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 7 deletions(-) diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index 8a52d1d..0508bc0 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -28,12 +28,6 @@ abstract class AbstractData */ protected static $_instance = null; - protected static $_namespaces = array( - 'purge_limiter', - 'salt', - 'traffic_limiter', - ); - /** * Enforce singleton, disable constructor * diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 607013b..5a0f369 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -423,6 +423,46 @@ class Database 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': + ; + break; + case 'salt': + ; + break; + case 'traffic_limiter': + ; + break; + default: + return false; + break; + } + } + + /** + * Load a value. + * + * @access public + * @param string $namespace + * @param string $key + * @return string + */ + public function getValue($namespace, $key = '') + { + + } + /** * Returns up to batch size number of paste ids that have expired * diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index fee61c1..76c2600 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -251,7 +251,20 @@ class Filesystem extends AbstractData */ public function setValue($value, $namespace, $key = '') { - + switch ($namespace) { + case 'purge_limiter': + ; + break; + case 'salt': + ; + break; + case 'traffic_limiter': + ; + break; + default: + return false; + break; + } } /** diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 1a1d8bf..81fcce6 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -217,6 +217,46 @@ class GoogleCloudStorage extends AbstractData return $o->exists(); } + /** + * 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': + ; + break; + case 'salt': + ; + break; + case 'traffic_limiter': + ; + break; + default: + return false; + break; + } + } + + /** + * Load a value. + * + * @access public + * @param string $namespace + * @param string $key + * @return string + */ + public function getValue($namespace, $key = '') + { + + } + /** * @inheritDoc */ From 55efc858b5d523c65581b999a2ab0def7e540c9b Mon Sep 17 00:00:00 2001 From: Mark van Holsteijn Date: Mon, 7 Jun 2021 09:11:24 +0200 Subject: [PATCH 124/478] simplest implementation of kv support on gcs --- lib/Data/GoogleCloudStorage.php | 59 ++++++++++++++++------------- tst/Data/GoogleCloudStorageTest.php | 25 ++++++++++-- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 81fcce6..ca1b340 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -218,43 +218,48 @@ class GoogleCloudStorage extends AbstractData } /** - * Save a value. - * - * @access public - * @param string $value - * @param string $namespace - * @param string $key - * @return bool + * This is the simplest thing that could possibly work. + * will be to tested for runtime performance. + * @inheritDoc */ public function setValue($value, $namespace, $key = '') { - switch ($namespace) { - case 'purge_limiter': - ; - break; - case 'salt': - ; - break; - case 'traffic_limiter': - ; - break; - default: - return false; - break; + $key = 'config/' . $namespace . '/' . $key; + $data = Json::encode($value); + + try { + $this->_bucket->upload($data, array( + 'name' => $key, + 'chunkSize' => 262144, + 'predefinedAcl' => 'private', + 'metadata' => array( + 'content-type' => 'application/json', + 'metadata' => array('namespace' => $namespace), + ), + )); + } catch (Exception $e) { + error_log('failed to set key ' . $key . ' to ' . $this->_bucket->name() . ', ' . + trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); + return false; } + return true; } /** - * Load a value. - * - * @access public - * @param string $namespace - * @param string $key - * @return string + * This is the simplest thing that could possibly work. + * will be to tested for runtime performance. + * @inheritDoc */ public function getValue($namespace, $key = '') { - + $key = 'config/' . $namespace . '/' . $key; + try { + $o = $this->_bucket->object($key); + $data = $o->downloadAsString(); + return Json::decode($data); + } catch (NotFoundException $e) { + return false; + } } /** diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 6905f04..30a7d5e 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -31,9 +31,6 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase public function setUp() { - // do not report E_NOTICE as fsouza/fake-gcs-server does not return a `generation` value in the response - // which the Google Cloud Storage PHP library expects. - error_reporting(E_ERROR | E_WARNING | E_PARSE); ini_set('error_log', stream_get_meta_data(tmpfile())['uri']); $this->_model = GoogleCloudStorage::getInstance(array( 'bucket' => self::$_bucket->name(), @@ -138,6 +135,28 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $this->assertFalse($this->_model->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), $comment), 'unable to store broken comment'); $this->assertFalse($this->_model->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment does still not exist'); } + + /** + * @throws Exception + */ + public function testKeyValueStore() + { + $salt = bin2hex(random_bytes(256)); + $this->_model->setValue($salt, 'salt', 'master'); + $storedSalt = $this->_model->getValue('salt', 'master'); + $this->assertEquals($salt, $storedSalt); + + $client = hash_hmac('sha512', '127.0.0.1', $salt); + $expire = time(); + $this->_model->setValue($expire, 'traffic_limiter', $client); + $storedExpired = $this->_model->getValue('traffic_limiter', $client); + $this->assertEquals($expire, $storedExpired); + + $purgeAt = $expire + (15 * 60); + $this->_model->setValue($purgeAt, 'purge_limiter', 'at'); + $storedPurgedAt = $this->_model->getValue('purge_limiter', 'at'); + $this->assertEquals($purgeAt, $storedPurgedAt); + } } /** From ae486d651be3a622dd1307bc0c681ecf5a894058 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 7 Jun 2021 21:53:42 +0200 Subject: [PATCH 125/478] folding Persistance\PurgeLimiter into Data\Filesystem --- lib/Data/Filesystem.php | 99 ++++++++++++++++--------- lib/Model.php | 1 + lib/Persistence/AbstractPersistence.php | 22 ++++++ lib/Persistence/PurgeLimiter.php | 14 +--- tst/Persistence/PurgeLimiterTest.php | 4 + 5 files changed, 94 insertions(+), 46 deletions(-) diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 76c2600..f24691b 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -253,7 +253,10 @@ class Filesystem extends AbstractData { switch ($namespace) { case 'purge_limiter': - ; + return self::_storeString( + self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php', + '_conf); + PurgeLimiter::setStore($this->_getStore()); if (PurgeLimiter::canPurge()) { $this->_getStore()->purge($this->_conf->getKey('batchsize', 'purge')); } diff --git a/lib/Persistence/AbstractPersistence.php b/lib/Persistence/AbstractPersistence.php index 489836d..73f0ab2 100644 --- a/lib/Persistence/AbstractPersistence.php +++ b/lib/Persistence/AbstractPersistence.php @@ -13,6 +13,7 @@ namespace PrivateBin\Persistence; use Exception; +use PrivateBin\Data\AbstractData; /** * AbstractPersistence @@ -30,6 +31,15 @@ abstract class AbstractPersistence */ private static $_path = 'data'; + /** + * data storage to use to persist something + * + * @access private + * @static + * @var AbstractData + */ + protected static $_store; + /** * set the path * @@ -42,6 +52,18 @@ abstract class AbstractPersistence self::$_path = $path; } + /** + * set the path + * + * @access public + * @static + * @param AbstractData $store + */ + public static function setStore(AbstractData $store) + { + self::$_store = $store; + } + /** * get the path * diff --git a/lib/Persistence/PurgeLimiter.php b/lib/Persistence/PurgeLimiter.php index ea07e32..19b83e2 100644 --- a/lib/Persistence/PurgeLimiter.php +++ b/lib/Persistence/PurgeLimiter.php @@ -71,17 +71,11 @@ 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'); return true; } } diff --git a/tst/Persistence/PurgeLimiterTest.php b/tst/Persistence/PurgeLimiterTest.php index 391a840..e8cedc0 100644 --- a/tst/Persistence/PurgeLimiterTest.php +++ b/tst/Persistence/PurgeLimiterTest.php @@ -1,5 +1,6 @@ _path); } PurgeLimiter::setPath($this->_path); + PurgeLimiter::setStore( + Filesystem::getInstance(array('dir' => $this->_path)) + ); } public function tearDown() From 3429d293d3ba0426924b5da488147e4f3b223c9f Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 8 Jun 2021 06:37:27 +0200 Subject: [PATCH 126/478] remove configurable dir for traffic & purge limiters --- CHANGELOG.md | 1 + cfg/conf.sample.php | 6 ------ lib/Configuration.php | 2 -- lib/Controller.php | 1 - lib/Persistence/PurgeLimiter.php | 3 +-- lib/Persistence/TrafficLimiter.php | 1 - tst/ConfigurationTest.php | 2 -- tst/ConfigurationTestGenerator.php | 4 ---- tst/ControllerTest.php | 3 --- tst/JsonApiTest.php | 2 -- tst/Persistence/PurgeLimiterTest.php | 1 - 11 files changed, 2 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c4321a..c546bd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * 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) * **1.3.5 (2021-04-05)** * ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan * ADDED: Make the project info configurable (#681) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index a4b7f6b..d362f3f 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -143,9 +143,6 @@ limit = 10 ; 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 @@ -157,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 diff --git a/lib/Configuration.php b/lib/Configuration.php index 1185440..7c4eb10 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -80,13 +80,11 @@ class Configuration 'traffic' => array( 'limit' => 10, 'header' => null, - 'dir' => 'data', 'exemptedIp' => null, ), 'purge' => array( 'limit' => 300, 'batchsize' => 10, - 'dir' => 'data', ), 'model' => array( 'class' => 'Filesystem', diff --git a/lib/Controller.php b/lib/Controller.php index 2df522a..72bd5b2 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -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'); diff --git a/lib/Persistence/PurgeLimiter.php b/lib/Persistence/PurgeLimiter.php index 19b83e2..6b60817 100644 --- a/lib/Persistence/PurgeLimiter.php +++ b/lib/Persistence/PurgeLimiter.php @@ -52,7 +52,6 @@ class PurgeLimiter extends AbstractPersistence public static function setConfiguration(Configuration $conf) { self::setLimit($conf->getKey('limit', 'purge')); - self::setPath($conf->getKey('dir', 'purge')); } /** @@ -71,7 +70,7 @@ class PurgeLimiter extends AbstractPersistence } $now = time(); - $pl = (int) self::$_store->getValue('purge_limiter'); + $pl = (int) self::$_store->getValue('purge_limiter'); if ($pl + self::$_limit >= $now) { return false; } diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index be76b8c..299c7a6 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -85,7 +85,6 @@ class TrafficLimiter extends AbstractPersistence public static function setConfiguration(Configuration $conf) { self::setLimit($conf->getKey('limit', 'traffic')); - self::setPath($conf->getKey('dir', 'traffic')); self::setExemptedIp($conf->getKey('exemptedIp', 'traffic')); if (($option = $conf->getKey('header', 'traffic')) !== null) { diff --git a/tst/ConfigurationTest.php b/tst/ConfigurationTest.php index 246618c..312b799 100644 --- a/tst/ConfigurationTest.php +++ b/tst/ConfigurationTest.php @@ -17,8 +17,6 @@ class ConfigurationTest extends PHPUnit_Framework_TestCase $this->_minimalConfig = '[main]' . PHP_EOL . '[model]' . PHP_EOL . '[model_options]'; $this->_options = Configuration::getDefaults(); $this->_options['model_options']['dir'] = PATH . $this->_options['model_options']['dir']; - $this->_options['traffic']['dir'] = PATH . $this->_options['traffic']['dir']; - $this->_options['purge']['dir'] = PATH . $this->_options['purge']['dir']; $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_cfg'; if (!is_dir($this->_path)) { mkdir($this->_path); diff --git a/tst/ConfigurationTestGenerator.php b/tst/ConfigurationTestGenerator.php index 284fa5f..945fc47 100755 --- a/tst/ConfigurationTestGenerator.php +++ b/tst/ConfigurationTestGenerator.php @@ -428,8 +428,6 @@ class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase Helper::confBackup(); $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; $this->_model = Filesystem::getInstance(array('dir' => $this->_path)); - ServerSalt::setPath($this->_path); - TrafficLimiter::setPath($this->_path); $this->reset(); } @@ -449,8 +447,6 @@ class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase if ($this->_model->exists(Helper::getPasteId())) $this->_model->delete(Helper::getPasteId()); $configuration['model_options']['dir'] = $this->_path; - $configuration['traffic']['dir'] = $this->_path; - $configuration['purge']['dir'] = $this->_path; Helper::createIniFile(CONF, $configuration); } diff --git a/tst/ControllerTest.php b/tst/ControllerTest.php index b00f2ce..6e7ec4c 100644 --- a/tst/ControllerTest.php +++ b/tst/ControllerTest.php @@ -37,11 +37,8 @@ class ControllerTest extends PHPUnit_Framework_TestCase $this->_data->delete(Helper::getPasteId()); } $options = parse_ini_file(CONF_SAMPLE, true); - $options['purge']['dir'] = $this->_path; - $options['traffic']['dir'] = $this->_path; $options['model_options']['dir'] = $this->_path; Helper::createIniFile(CONF, $options); - ServerSalt::setPath($this->_path); } /** diff --git a/tst/JsonApiTest.php b/tst/JsonApiTest.php index 9655e60..17b699f 100644 --- a/tst/JsonApiTest.php +++ b/tst/JsonApiTest.php @@ -25,8 +25,6 @@ class JsonApiTest extends PHPUnit_Framework_TestCase $this->_model->delete(Helper::getPasteId()); } $options = parse_ini_file(CONF_SAMPLE, true); - $options['purge']['dir'] = $this->_path; - $options['traffic']['dir'] = $this->_path; $options['model_options']['dir'] = $this->_path; Helper::confBackup(); Helper::createIniFile(CONF, $options); diff --git a/tst/Persistence/PurgeLimiterTest.php b/tst/Persistence/PurgeLimiterTest.php index e8cedc0..adb96ff 100644 --- a/tst/Persistence/PurgeLimiterTest.php +++ b/tst/Persistence/PurgeLimiterTest.php @@ -14,7 +14,6 @@ class PurgeLimiterTest extends PHPUnit_Framework_TestCase if (!is_dir($this->_path)) { mkdir($this->_path); } - PurgeLimiter::setPath($this->_path); PurgeLimiter::setStore( Filesystem::getInstance(array('dir' => $this->_path)) ); From b5a6ce323e04318fba7a45a089bd6c872cd5b89c Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 8 Jun 2021 07:49:22 +0200 Subject: [PATCH 127/478] folding Persistance\TrafficLimiter into Data\Filesystem --- lib/Controller.php | 1 + lib/Data/AbstractData.php | 10 +++++++ lib/Data/Database.php | 17 +++++++++++ lib/Data/Filesystem.php | 45 ++++++++++++++++++++++++++++-- lib/Data/GoogleCloudStorage.php | 17 +++++++++++ lib/Model.php | 8 +++--- lib/Persistence/TrafficLimiter.php | 31 +++++--------------- tst/ControllerTest.php | 1 + 8 files changed, 100 insertions(+), 30 deletions(-) diff --git a/lib/Controller.php b/lib/Controller.php index 72bd5b2..095bbeb 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -198,6 +198,7 @@ class Controller try { // Ensure last paste from visitors IP address was more than configured amount of seconds ago. TrafficLimiter::setConfiguration($this->_conf); + TrafficLimiter::setStore($this->_model->getStore()); if (!TrafficLimiter::canPass()) { $this->_return_message( 1, I18n::_( diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index 0508bc0..3de4bab 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -130,6 +130,16 @@ abstract class AbstractData */ abstract public function existsComment($pasteid, $parentid, $commentid); + /** + * Purge outdated entries. + * + * @access public + * @param string $namespace + * @param int $time + * @return void + */ + abstract public function purgeValues($namespace, $time); + /** * Save a value. * diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 5a0f369..2a1bbcb 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -423,6 +423,23 @@ class Database extends AbstractData ); } + /** + * Purge outdated entries. + * + * @access public + * @param string $namespace + * @param int $time + * @return void + */ + public function purgeValues($namespace, $time) + { + switch ($namespace) { + case 'traffic_limiter': + ; + break; + } + } + /** * Save a value. * diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index f24691b..3a481d9 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -38,6 +38,15 @@ class Filesystem extends AbstractData */ private static $_path = 'data'; + /** + * cache for the traffic limiter + * + * @access private + * @static + * @var array + */ + private static $_traffic_limiter_cache = array(); + /** * get instance of singleton * @@ -240,6 +249,27 @@ class Filesystem extends AbstractData ); } + /** + * Purge outdated entries. + * + * @access public + * @param string $namespace + * @param int $time + * @return void + */ + public function purgeValues($namespace, $time) + { + switch ($namespace) { + case 'traffic_limiter': + foreach (self::$_traffic_limiter_cache as $key => $last_access) { + if ($last_access <= $time) { + unset(self::$_traffic_limiter_cache[$key]); + } + } + break; + } + } + /** * Save a value. * @@ -262,7 +292,11 @@ class Filesystem extends AbstractData ; break; case 'traffic_limiter': - ; + self::$_traffic_limiter_cache[$key] = $value; + return self::_storeString( + self::$_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php', + 'exists(); } + /** + * Purge outdated entries. + * + * @access public + * @param string $namespace + * @param int $time + * @return void + */ + public function purgeValues($namespace, $time) + { + switch ($namespace) { + case 'traffic_limiter': + ; + break; + } + } + /** * This is the simplest thing that could possibly work. * will be to tested for runtime performance. diff --git a/lib/Model.php b/lib/Model.php index 1a23653..8aebd79 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -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,9 +67,9 @@ class Model public function purge() { PurgeLimiter::setConfiguration($this->_conf); - PurgeLimiter::setStore($this->_getStore()); + PurgeLimiter::setStore($this->getStore()); if (PurgeLimiter::canPurge()) { - $this->_getStore()->purge($this->_conf->getKey('batchsize', 'purge')); + $this->getStore()->purge($this->_conf->getKey('batchsize', 'purge')); } } @@ -78,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/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 299c7a6..f6bc07f 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -169,35 +169,18 @@ class TrafficLimiter extends AbstractPersistence } } - $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]); - } - } - // 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 = 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; } - self::_store( - $file, - 'setValue((string) $tl, 'traffic_limiter'); return $result; } } diff --git a/tst/ControllerTest.php b/tst/ControllerTest.php index 6e7ec4c..92683ce 100644 --- a/tst/ControllerTest.php +++ b/tst/ControllerTest.php @@ -17,6 +17,7 @@ class ControllerTest extends PHPUnit_Framework_TestCase /* Setup Routine */ $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; $this->_data = Filesystem::getInstance(array('dir' => $this->_path)); + TrafficLimiter::setStore($this->_data); $this->reset(); } From 7901ec74a7167c6fb65866826d7c21e936427c4c Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 8 Jun 2021 22:01:29 +0200 Subject: [PATCH 128/478] folding Persistance\ServerSalt into Data\Filesystem --- lib/Controller.php | 1 + lib/Data/Filesystem.php | 22 +++-- lib/Model/Paste.php | 2 +- lib/Persistence/AbstractPersistence.php | 113 ------------------------ lib/Persistence/ServerSalt.php | 23 ++--- lib/Persistence/TrafficLimiter.php | 4 +- tst/ControllerTest.php | 22 ----- tst/JsonApiTest.php | 2 +- tst/ModelTest.php | 2 +- tst/Persistence/ServerSaltTest.php | 64 ++++++++------ tst/Persistence/TrafficLimiterTest.php | 18 +++- tst/Vizhash16x16Test.php | 3 +- 12 files changed, 80 insertions(+), 196 deletions(-) diff --git a/lib/Controller.php b/lib/Controller.php index 095bbeb..4a1aa0d 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -197,6 +197,7 @@ class Controller { try { // Ensure last paste from visitors IP address was more than configured amount of seconds ago. + ServerSalt::setStore($this->_model->getStore()); TrafficLimiter::setConfiguration($this->_conf); TrafficLimiter::setStore($this->_model->getStore()); if (!TrafficLimiter::canPass()) { diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 3a481d9..8b443ad 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -287,17 +287,17 @@ class Filesystem extends AbstractData self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php', '_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 73f0ab2..4e61a8e 100644 --- a/lib/Persistence/AbstractPersistence.php +++ b/lib/Persistence/AbstractPersistence.php @@ -12,7 +12,6 @@ namespace PrivateBin\Persistence; -use Exception; use PrivateBin\Data\AbstractData; /** @@ -22,15 +21,6 @@ use PrivateBin\Data\AbstractData; */ abstract class AbstractPersistence { - /** - * path in which to persist something - * - * @access private - * @static - * @var string - */ - private static $_path = 'data'; - /** * data storage to use to persist something * @@ -40,18 +30,6 @@ abstract class AbstractPersistence */ protected static $_store; - /** - * set the path - * - * @access public - * @static - * @param string $path - */ - public static function setPath($path) - { - self::$_path = $path; - } - /** * set the path * @@ -63,95 +41,4 @@ abstract class AbstractPersistence { self::$_store = $store; } - - /** - * 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 = 0; - if ($fileCreated = @touch($file)) { - $writtenBytes = @file_put_contents( - $file, - 'Require all denied' . PHP_EOL, - LOCK_EX - ); - } - if ($fileCreated === false || $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; - $fileCreated = true; - $writtenBytes = 0; - if (!is_file($file)) { - $fileCreated = @touch($file); - } - if ($fileCreated) { - $writtenBytes = @file_put_contents($file, $data, LOCK_EX); - } - if ($fileCreated === false || $writtenBytes === false || $writtenBytes < strlen($data)) { - throw new Exception('unable to write to file ' . $file, 13); - } - @chmod($file, 0640); // protect file access - return $file; - } } diff --git a/lib/Persistence/ServerSalt.php b/lib/Persistence/ServerSalt.php index 329a8ef..93f5486 100644 --- a/lib/Persistence/ServerSalt.php +++ b/lib/Persistence/ServerSalt.php @@ -13,6 +13,7 @@ namespace PrivateBin\Persistence; use Exception; +use PrivateBin\Data\AbstractData; /** * ServerSalt @@ -71,20 +72,12 @@ 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'); } return self::$_salt; } @@ -94,11 +87,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 f6bc07f..1146861 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -172,7 +172,7 @@ class TrafficLimiter extends AbstractPersistence // this hash is used as an array key, hence a shorter algo is used $hash = self::getHash('sha256'); $now = time(); - $tl = self::$_store->getValue('traffic_limiter', $hash); + $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; @@ -180,7 +180,7 @@ class TrafficLimiter extends AbstractPersistence $tl = time(); $result = true; } - self::$_store->setValue((string) $tl, 'traffic_limiter'); + self::$_store->setValue((string) $tl, 'traffic_limiter', $hash); return $result; } } diff --git a/tst/ControllerTest.php b/tst/ControllerTest.php index 92683ce..165ae08 100644 --- a/tst/ControllerTest.php +++ b/tst/ControllerTest.php @@ -125,28 +125,6 @@ class ControllerTest extends PHPUnit_Framework_TestCase ); } - /** - * @runInSeparateProcess - */ - public function testHtaccess() - { - $htaccess = $this->_path . DIRECTORY_SEPARATOR . '.htaccess'; - @unlink($htaccess); - - $paste = Helper::getPasteJson(); - $file = tempnam(sys_get_temp_dir(), 'FOO'); - file_put_contents($file, $paste); - Request::setInputStream($file); - $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest'; - $_SERVER['REQUEST_METHOD'] = 'POST'; - $_SERVER['REMOTE_ADDR'] = '::1'; - ob_start(); - new Controller; - ob_end_clean(); - - $this->assertFileExists($htaccess, 'htaccess recreated'); - } - /** * @expectedException Exception * @expectedExceptionCode 2 diff --git a/tst/JsonApiTest.php b/tst/JsonApiTest.php index 17b699f..6d9732a 100644 --- a/tst/JsonApiTest.php +++ b/tst/JsonApiTest.php @@ -16,7 +16,7 @@ class JsonApiTest extends PHPUnit_Framework_TestCase /* Setup Routine */ $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; $this->_model = Filesystem::getInstance(array('dir' => $this->_path)); - ServerSalt::setPath($this->_path); + ServerSalt::setStore($this->_model); $_POST = array(); $_GET = array(); diff --git a/tst/ModelTest.php b/tst/ModelTest.php index d5c4074..478d4c1 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -25,7 +25,6 @@ class ModelTest extends PHPUnit_Framework_TestCase if (!is_dir($this->_path)) { mkdir($this->_path); } - ServerSalt::setPath($this->_path); $options = parse_ini_file(CONF_SAMPLE, true); $options['purge']['limit'] = 0; $options['model'] = array( @@ -39,6 +38,7 @@ class ModelTest extends PHPUnit_Framework_TestCase ); Helper::confBackup(); Helper::createIniFile(CONF, $options); + ServerSalt::setStore(Database::getInstance($options['model_options'])); $this->_conf = new Configuration; $this->_model = new Model($this->_conf); $_SERVER['REMOTE_ADDR'] = '::1'; diff --git a/tst/Persistence/ServerSaltTest.php b/tst/Persistence/ServerSaltTest.php index ecdc0f8..3db5f7d 100644 --- a/tst/Persistence/ServerSaltTest.php +++ b/tst/Persistence/ServerSaltTest.php @@ -1,5 +1,6 @@ _path)) { mkdir($this->_path); } - ServerSalt::setPath($this->_path); + ServerSalt::setStore( + Filesystem::getInstance(array('dir' => $this->_path)) + ); $this->_otherPath = $this->_path . DIRECTORY_SEPARATOR . 'foo'; @@ -40,46 +43,46 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase public function testGeneration() { // generating new salt - ServerSalt::setPath($this->_path); + ServerSalt::setStore( + Filesystem::getInstance(array('dir' => $this->_path)) + ); $salt = ServerSalt::get(); // try setting a different path and resetting it - ServerSalt::setPath($this->_otherPath); + ServerSalt::setStore( + Filesystem::getInstance(array('dir' => $this->_otherPath)) + ); $this->assertNotEquals($salt, ServerSalt::get()); - ServerSalt::setPath($this->_path); + ServerSalt::setStore( + Filesystem::getInstance(array('dir' => $this->_path)) + ); $this->assertEquals($salt, ServerSalt::get()); } - /** - * @expectedException Exception - * @expectedExceptionCode 11 - */ public function testPathShenanigans() { // try setting an invalid path chmod($this->_invalidPath, 0000); - ServerSalt::setPath($this->_invalidPath); - ServerSalt::get(); + $store = Filesystem::getInstance(array('dir' => $this->_invalidPath)); + ServerSalt::setStore($store); + $salt = ServerSalt::get(); + ServerSalt::setStore($store); + $this->assertNotEquals($salt, ServerSalt::get()); } - /** - * @expectedException Exception - * @expectedExceptionCode 20 - */ public function testFileRead() { // try setting an invalid file chmod($this->_invalidPath, 0700); file_put_contents($this->_invalidFile, ''); chmod($this->_invalidFile, 0000); - ServerSalt::setPath($this->_invalidPath); - ServerSalt::get(); + $store = Filesystem::getInstance(array('dir' => $this->_invalidPath)); + ServerSalt::setStore($store); + $salt = ServerSalt::get(); + ServerSalt::setStore($store); + $this->assertNotEquals($salt, ServerSalt::get()); } - /** - * @expectedException Exception - * @expectedExceptionCode 13 - */ public function testFileWrite() { // try setting an invalid file @@ -90,19 +93,24 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase } file_put_contents($this->_invalidPath . DIRECTORY_SEPARATOR . '.htaccess', ''); chmod($this->_invalidPath, 0500); - ServerSalt::setPath($this->_invalidPath); - ServerSalt::get(); + $store = Filesystem::getInstance(array('dir' => $this->_invalidPath)); + ServerSalt::setStore($store); + $salt = ServerSalt::get(); + ServerSalt::setStore($store); + $this->assertNotEquals($salt, ServerSalt::get()); } - /** - * @expectedException Exception - * @expectedExceptionCode 10 - */ public function testPermissionShenanigans() { // try creating an invalid path chmod($this->_invalidPath, 0000); - ServerSalt::setPath($this->_invalidPath . DIRECTORY_SEPARATOR . 'baz'); - ServerSalt::get(); + ServerSalt::setStore( + Filesystem::getInstance(array('dir' => $this->_invalidPath . DIRECTORY_SEPARATOR . 'baz')) + ); + $store = Filesystem::getInstance(array('dir' => $this->_invalidPath)); + ServerSalt::setStore($store); + $salt = ServerSalt::get(); + ServerSalt::setStore($store); + $this->assertNotEquals($salt, ServerSalt::get()); } } diff --git a/tst/Persistence/TrafficLimiterTest.php b/tst/Persistence/TrafficLimiterTest.php index 4101301..2e81d83 100644 --- a/tst/Persistence/TrafficLimiterTest.php +++ b/tst/Persistence/TrafficLimiterTest.php @@ -1,5 +1,7 @@ _path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'trafficlimit'; - TrafficLimiter::setPath($this->_path); + $store = Filesystem::getInstance(array('dir' => $this->_path)); + ServerSalt::setStore($store); + TrafficLimiter::setStore($store); } public function tearDown() @@ -19,11 +23,17 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase Helper::rmDir($this->_path . DIRECTORY_SEPARATOR); } + public function testHtaccess() + { + $htaccess = $this->_path . DIRECTORY_SEPARATOR . '.htaccess'; + @unlink($htaccess); + $_SERVER['REMOTE_ADDR'] = 'foobar'; + TrafficLimiter::canPass(); + $this->assertFileExists($htaccess, 'htaccess recreated'); + } + public function testTrafficGetsLimited() { - $this->assertEquals($this->_path, TrafficLimiter::getPath()); - $file = 'baz'; - $this->assertEquals($this->_path . DIRECTORY_SEPARATOR . $file, TrafficLimiter::getPath($file)); TrafficLimiter::setLimit(4); $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; $this->assertTrue(TrafficLimiter::canPass(), 'first request may pass'); diff --git a/tst/Vizhash16x16Test.php b/tst/Vizhash16x16Test.php index afcda56..abfb8c4 100644 --- a/tst/Vizhash16x16Test.php +++ b/tst/Vizhash16x16Test.php @@ -1,5 +1,6 @@ _path); } $this->_file = $this->_path . DIRECTORY_SEPARATOR . 'vizhash.png'; - ServerSalt::setPath($this->_path); + ServerSalt::setStore(Filesystem::getInstance(array('dir' => $this->_path))); } public function tearDown() From a203e6322b16823b126f0538313d79855ced7560 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 9 Jun 2021 07:47:40 +0200 Subject: [PATCH 129/478] implementing key/value store of Persistance in Database storage --- CHANGELOG.md | 1 + lib/Data/AbstractData.php | 20 +++++++++- lib/Data/Database.php | 71 +++++++++++++++++++-------------- lib/Data/Filesystem.php | 30 -------------- lib/Data/GoogleCloudStorage.php | 9 +++-- tst/ControllerTest.php | 1 + tst/ControllerWithDbTest.php | 4 ++ tst/Data/DatabaseTest.php | 15 +++++++ 8 files changed, 86 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c546bd1..d964a57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * 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) diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index 3de4bab..6f2dee5 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -28,6 +28,15 @@ abstract class AbstractData */ protected static $_instance = null; + /** + * cache for the traffic limiter + * + * @access private + * @static + * @var array + */ + protected static $_traffic_limiter_cache = array(); + /** * Enforce singleton, disable constructor * @@ -138,7 +147,16 @@ abstract class AbstractData * @param int $time * @return void */ - abstract public function purgeValues($namespace, $time); + public function purgeValues($namespace, $time) + { + if ($namespace === 'traffic_limiter') { + foreach (self::$_traffic_limiter_cache as $key => $last_access) { + if ($last_access <= $time) { + unset(self::$_traffic_limiter_cache[$key]); + } + } + } + } /** * Save a value. diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 2a1bbcb..ca0cc79 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -423,23 +423,6 @@ class Database extends AbstractData ); } - /** - * Purge outdated entries. - * - * @access public - * @param string $namespace - * @param int $time - * @return void - */ - public function purgeValues($namespace, $time) - { - switch ($namespace) { - case 'traffic_limiter': - ; - break; - } - } - /** * Save a value. * @@ -451,20 +434,19 @@ class Database extends AbstractData */ public function setValue($value, $namespace, $key = '') { - switch ($namespace) { - case 'purge_limiter': - ; - break; - case 'salt': - ; - break; - case 'traffic_limiter': - ; - break; - default: + if ($namespace === 'traffic_limiter') { + self::$_traffic_limiter_cache[$key] = $value; + try { + $value = Json::encode(self::$_traffic_limiter_cache); + } catch (Exception $e) { return false; - break; + } } + return self::_exec( + 'UPDATE ' . self::_sanitizeIdentifier('config') . + ' SET value = ? WHERE id = ?', + array($value, strtoupper($namespace)) + ); } /** @@ -477,7 +459,36 @@ class Database extends AbstractData */ 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::$_traffic_limiter_cache = Json::decode($value); + } catch (Exception $e) { + self::$_traffic_limiter_cache = array(); + } + if (array_key_exists($key, self::$_traffic_limiter_cache)) { + return self::$_traffic_limiter_cache[$key]; + } + } + return (string) $value; } /** @@ -629,7 +640,7 @@ class Database extends AbstractData 'SELECT value FROM ' . self::_sanitizeIdentifier('config') . ' WHERE id = ?', array($key), true ); - return $row['value']; + return $row ? $row['value']: ''; } /** diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 8b443ad..384c72d 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -38,15 +38,6 @@ class Filesystem extends AbstractData */ private static $_path = 'data'; - /** - * cache for the traffic limiter - * - * @access private - * @static - * @var array - */ - private static $_traffic_limiter_cache = array(); - /** * get instance of singleton * @@ -249,27 +240,6 @@ class Filesystem extends AbstractData ); } - /** - * Purge outdated entries. - * - * @access public - * @param string $namespace - * @param int $time - * @return void - */ - public function purgeValues($namespace, $time) - { - switch ($namespace) { - case 'traffic_limiter': - foreach (self::$_traffic_limiter_cache as $key => $last_access) { - if ($last_access <= $time) { - unset(self::$_traffic_limiter_cache[$key]); - } - } - break; - } - } - /** * Save a value. * diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index f6fc489..2521957 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -227,10 +227,11 @@ class GoogleCloudStorage extends AbstractData */ public function purgeValues($namespace, $time) { - switch ($namespace) { - case 'traffic_limiter': - ; - break; + if ($namespace === 'traffic_limiter') { + // TODO implement purging of keys in namespace that are <= $time + // if GCS has no easy way to iterate all keys, consider using the + // self::$_traffic_limiter_cache in a similar way as the other + // implementations. } } diff --git a/tst/ControllerTest.php b/tst/ControllerTest.php index 165ae08..0aa7d79 100644 --- a/tst/ControllerTest.php +++ b/tst/ControllerTest.php @@ -17,6 +17,7 @@ class ControllerTest extends PHPUnit_Framework_TestCase /* Setup Routine */ $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; $this->_data = Filesystem::getInstance(array('dir' => $this->_path)); + ServerSalt::setStore($this->_data); TrafficLimiter::setStore($this->_data); $this->reset(); } diff --git a/tst/ControllerWithDbTest.php b/tst/ControllerWithDbTest.php index 90dee92..bc8cd7b 100644 --- a/tst/ControllerWithDbTest.php +++ b/tst/ControllerWithDbTest.php @@ -1,6 +1,8 @@ _options['dsn'] = 'sqlite:' . $this->_path . DIRECTORY_SEPARATOR . 'tst.sq3'; $this->_data = Database::getInstance($this->_options); + ServerSalt::setStore($this->_data); + TrafficLimiter::setStore($this->_data); $this->reset(); } diff --git a/tst/Data/DatabaseTest.php b/tst/Data/DatabaseTest.php index 0d48eb6..f9cd265 100644 --- a/tst/Data/DatabaseTest.php +++ b/tst/Data/DatabaseTest.php @@ -2,6 +2,8 @@ use PrivateBin\Controller; use PrivateBin\Data\Database; +use PrivateBin\Data\Filesystem; +use PrivateBin\Persistence\ServerSalt; class DatabaseTest extends PHPUnit_Framework_TestCase { @@ -31,6 +33,19 @@ class DatabaseTest extends PHPUnit_Framework_TestCase } } + public function testSaltMigration() + { + ServerSalt::setStore(Filesystem::getInstance(array('dir' => 'data'))); + $salt = ServerSalt::get(); + $file = 'data' . DIRECTORY_SEPARATOR . 'salt.php'; + $this->assertFileExists($file, 'ServerSalt got initialized and stored on disk'); + $this->assertNotEquals($salt, ''); + ServerSalt::setStore($this->_model); + ServerSalt::get(); + $this->assertFileNotExists($file, 'legacy ServerSalt got removed'); + $this->assertEquals($salt, ServerSalt::get(), 'ServerSalt got preserved & migrated'); + } + public function testDatabaseBasedDataStoreWorks() { $this->_model->delete(Helper::getPasteId()); From 7b2f0ff302a24c15a7597c23e3bf03adfabc2dca Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 9 Jun 2021 19:16:22 +0200 Subject: [PATCH 130/478] apply StyleCI recommendation --- lib/Data/Database.php | 4 ++-- lib/Persistence/TrafficLimiter.php | 4 ++-- tst/Persistence/TrafficLimiterTest.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index ca0cc79..ae4da19 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -460,7 +460,7 @@ class Database extends AbstractData public function getValue($namespace, $key = '') { $configKey = strtoupper($namespace); - $value = $this->_getConfig($configKey); + $value = $this->_getConfig($configKey); if ($value === '') { // initialize the row, so that setValue can rely on UPDATE queries self::_exec( @@ -640,7 +640,7 @@ class Database extends AbstractData 'SELECT value FROM ' . self::_sanitizeIdentifier('config') . ' WHERE id = ?', array($key), true ); - return $row ? $row['value']: ''; + return $row ? $row['value'] : ''; } /** diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 1146861..930272d 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -171,8 +171,8 @@ class TrafficLimiter extends AbstractPersistence // this hash is used as an array key, hence a shorter algo is used $hash = self::getHash('sha256'); - $now = time(); - $tl = (int) self::$_store->getValue('traffic_limiter', $hash); + $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; diff --git a/tst/Persistence/TrafficLimiterTest.php b/tst/Persistence/TrafficLimiterTest.php index 2e81d83..aedbf88 100644 --- a/tst/Persistence/TrafficLimiterTest.php +++ b/tst/Persistence/TrafficLimiterTest.php @@ -12,7 +12,7 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase { /* Setup Routine */ $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'trafficlimit'; - $store = Filesystem::getInstance(array('dir' => $this->_path)); + $store = Filesystem::getInstance(array('dir' => $this->_path)); ServerSalt::setStore($store); TrafficLimiter::setStore($store); } From 1232717334aa2ff65edf3381a21152bb6e1b02cd Mon Sep 17 00:00:00 2001 From: Mark van Holsteijn Date: Wed, 9 Jun 2021 22:27:34 +0200 Subject: [PATCH 131/478] added purgeValues to GCS --- lib/Data/GoogleCloudStorage.php | 37 ++++++++++++++++++++--------- tst/Data/GoogleCloudStorageTest.php | 22 ++++++++++++----- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 2521957..75bb8a6 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -2,6 +2,7 @@ namespace PrivateBin\Data; +use DateTime; use Exception; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Storage\StorageClient; @@ -9,6 +10,8 @@ use PrivateBin\Json; class GoogleCloudStorage extends AbstractData { + const DATETIME_FORMAT = 'Y-m-d\TH:i:s.u\Z'; + /** * returns a Google Cloud Storage data backend. * @@ -218,20 +221,32 @@ class GoogleCloudStorage extends AbstractData } /** - * Purge outdated entries. - * - * @access public - * @param string $namespace - * @param int $time - * @return void + * @inheritDoc */ public function purgeValues($namespace, $time) { - if ($namespace === 'traffic_limiter') { - // TODO implement purging of keys in namespace that are <= $time - // if GCS has no easy way to iterate all keys, consider using the - // self::$_traffic_limiter_cache in a similar way as the other - // implementations. + $prefix = 'config/' . $namespace . '/'; + try { + foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) { + $info = $object->info(); + $timeCreated = false; + if (key_exists('timeCreated', $info)) { + $timeCreated = DateTime::createFromFormat(GoogleCloudStorage::DATETIME_FORMAT, $info['timeCreated']); + } + if ($timeCreated && ($timeCreated->getTimestamp() < $time)) { + try { + $object->delete(); + } catch (NotFoundException $e) { + // deleted by another instance. + } + } else { + if (!$timeCreated) { + error_log('failed to parse create timestamp ' . $info['timeCreated'] . ' of object ' . $object->name()); + } + } + } + } catch (NotFoundException $e) { + // no objects in the bucket yet } } diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 30a7d5e..1c5190d 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -43,7 +43,6 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase foreach (self::$_bucket->objects() as $object) { $object->delete(); } - error_reporting(E_ALL); } public static function tearDownAfterClass() @@ -145,17 +144,26 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $this->_model->setValue($salt, 'salt', 'master'); $storedSalt = $this->_model->getValue('salt', 'master'); $this->assertEquals($salt, $storedSalt); + $this->_model->purgeValues('salt', time() + 60); + $this->assertFalse($this->_model->getValue('salt', 'master')); $client = hash_hmac('sha512', '127.0.0.1', $salt); $expire = time(); $this->_model->setValue($expire, 'traffic_limiter', $client); $storedExpired = $this->_model->getValue('traffic_limiter', $client); $this->assertEquals($expire, $storedExpired); + $this->assertEquals($expire, $storedExpired); + $this->_model->purgeValues('traffic_limiter', time() - 60); + $this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client)); + $this->_model->purgeValues('traffic_limiter', time() + 60); + $this->assertFalse($this->_model->getValue('traffic_limiter', $client)); $purgeAt = $expire + (15 * 60); $this->_model->setValue($purgeAt, 'purge_limiter', 'at'); $storedPurgedAt = $this->_model->getValue('purge_limiter', 'at'); $this->assertEquals($purgeAt, $storedPurgedAt); + $this->_model->purgeValues('purge_limiter', time() + 60); + $this->assertFalse($this->_model->getValue('purge_limiter', 'at')); } } @@ -431,11 +439,13 @@ class StorageObjectStub extends StorageObject public function __construct(ConnectionInterface $connection, $name, $bucket, $generation = null, array $info = array(), $encryptionKey = null, $encryptionKeySHA256 = null) { - $this->_name = $name; - $this->_bucket = $bucket; - $this->_generation = $generation; - $this->_info = $info; - $this->_connection = $connection; + $this->_name = $name; + $this->_bucket = $bucket; + $this->_generation = $generation; + $this->_info = $info; + $this->_connection = $connection; + $timeCreated = new Datetime(); + $this->_info['metadata']['timeCreated'] = $timeCreated->format(GoogleCloudStorage::DATETIME_FORMAT); } public function acl() From 1b88eef3569f39c0596d2fb5b39d23301f65af0f Mon Sep 17 00:00:00 2001 From: Mark van Holsteijn Date: Thu, 10 Jun 2021 21:39:15 +0200 Subject: [PATCH 132/478] improved implementation of GoogleStorageBucket --- lib/Data/GoogleCloudStorage.php | 55 +++++++++++++++++------------ tst/Data/GoogleCloudStorageTest.php | 43 +++++++++++++++++++--- 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 75bb8a6..6bbea60 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -2,7 +2,6 @@ namespace PrivateBin\Data; -use DateTime; use Exception; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Storage\StorageClient; @@ -225,23 +224,22 @@ class GoogleCloudStorage extends AbstractData */ public function purgeValues($namespace, $time) { - $prefix = 'config/' . $namespace . '/'; + $path = 'config/' . $namespace; try { - foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) { - $info = $object->info(); - $timeCreated = false; - if (key_exists('timeCreated', $info)) { - $timeCreated = DateTime::createFromFormat(GoogleCloudStorage::DATETIME_FORMAT, $info['timeCreated']); + foreach ($this->_bucket->objects(array('prefix' => $path)) as $object) { + $name = $object->name(); + if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') { + continue; } - if ($timeCreated && ($timeCreated->getTimestamp() < $time)) { - try { - $object->delete(); - } catch (NotFoundException $e) { - // deleted by another instance. - } - } else { - if (!$timeCreated) { - error_log('failed to parse create timestamp ' . $info['timeCreated'] . ' of object ' . $object->name()); + $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. + } } } } @@ -251,15 +249,24 @@ class GoogleCloudStorage extends AbstractData } /** - * This is the simplest thing that could possibly work. - * will be to tested for runtime performance. + * 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 = '') { - $key = 'config/' . $namespace . '/' . $key; + if ($key === '') { + $key = 'config/' . $namespace; + } else { + $key = 'config/' . $namespace . '/' . $key; + } + $data = Json::encode($value); + $metadata = array('namespace' => $namespace); + if ($namespace != 'salt') { + $metadata['value'] = strval($value); + } try { $this->_bucket->upload($data, array( 'name' => $key, @@ -267,7 +274,7 @@ class GoogleCloudStorage extends AbstractData 'predefinedAcl' => 'private', 'metadata' => array( 'content-type' => 'application/json', - 'metadata' => array('namespace' => $namespace), + 'metadata' => $metadata, ), )); } catch (Exception $e) { @@ -279,13 +286,15 @@ class GoogleCloudStorage extends AbstractData } /** - * This is the simplest thing that could possibly work. - * will be to tested for runtime performance. * @inheritDoc */ public function getValue($namespace, $key = '') { - $key = 'config/' . $namespace . '/' . $key; + if ($key === '') { + $key = 'config/' . $namespace; + } else { + $key = 'config/' . $namespace . '/' . $key; + } try { $o = $this->_bucket->object($key); $data = $o->downloadAsString(); diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 1c5190d..7945e2b 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -141,8 +141,8 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase public function testKeyValueStore() { $salt = bin2hex(random_bytes(256)); - $this->_model->setValue($salt, 'salt', 'master'); - $storedSalt = $this->_model->getValue('salt', 'master'); + $this->_model->setValue($salt, 'salt', ''); + $storedSalt = $this->_model->getValue('salt', ''); $this->assertEquals($salt, $storedSalt); $this->_model->purgeValues('salt', time() + 60); $this->assertFalse($this->_model->getValue('salt', 'master')); @@ -159,12 +159,47 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $this->assertFalse($this->_model->getValue('traffic_limiter', $client)); $purgeAt = $expire + (15 * 60); - $this->_model->setValue($purgeAt, 'purge_limiter', 'at'); - $storedPurgedAt = $this->_model->getValue('purge_limiter', 'at'); + $this->_model->setValue($purgeAt, 'purge_limiter', ''); + $storedPurgedAt = $this->_model->getValue('purge_limiter', ''); $this->assertEquals($purgeAt, $storedPurgedAt); $this->_model->purgeValues('purge_limiter', time() + 60); $this->assertFalse($this->_model->getValue('purge_limiter', 'at')); } + + /** + * @throws Exception + */ + public function testKeyValuePurgeTrafficLimiter() + { + $salt = bin2hex(random_bytes(256)); + $client = hash_hmac('sha512', '127.0.0.1', $salt); + $expire = time(); + $this->_model->setValue($expire, 'traffic_limiter', $client); + $storedExpired = $this->_model->getValue('traffic_limiter', $client); + $this->assertEquals($expire, $storedExpired); + + $this->_model->purgeValues('traffic_limiter', time() - 60); + $this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client)); + + $this->_model->purgeValues('traffic_limiter', time() + 60); + $this->assertFalse($this->_model->getValue('traffic_limiter', $client)); + } + + public function testKeyValuePurgeTrafficLimiterWithKey() + { + $salt = bin2hex(random_bytes(256)); + $client = hash_hmac('sha512', '127.0.0.1', $salt); + $expire = time(); + $this->_model->setValue($expire, 'traffic_limiter', $client); + $storedExpired = $this->_model->getValue('traffic_limiter', $client); + $this->assertEquals($expire, $storedExpired); + + $this->_model->purgeValues('traffic_limiter', time() - 60); + $this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client)); + + $this->_model->purgeValues('traffic_limiter', time() + 60); + $this->assertFalse($this->_model->getValue('traffic_limiter', $client)); + } } /** From e294145a2bdf52753ad0ca55d8091e8160068490 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 13 Jun 2021 08:26:05 +0200 Subject: [PATCH 133/478] ip-lib doesn't except on the matches interfaces --- lib/Persistence/TrafficLimiter.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 930272d..4f11ec7 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -13,7 +13,6 @@ namespace PrivateBin\Persistence; -use Exception; use IPLib\Factory; use PrivateBin\Configuration; @@ -133,13 +132,7 @@ class TrafficLimiter extends AbstractPersistence return false; } - // Ip-lib throws an exception when something goes wrong, if so we want to catch it and set contained to false - try { - return $address->matches($range); - } catch (Exception $e) { - // If something is wrong with matching the ip, we assume it doesn't match - return false; - } + return $address->matches($range); } /** @@ -149,7 +142,6 @@ class TrafficLimiter extends AbstractPersistence * * @access public * @static - * @throws Exception * @return bool */ public static function canPass() From 93135e0abf92ebce8cafee5299769e1434f24bf2 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 13 Jun 2021 10:44:26 +0200 Subject: [PATCH 134/478] improving code coverage --- lib/Controller.php | 27 +- lib/Data/Database.php | 78 ++-- lib/Data/GoogleCloudStorage.php | 17 +- lib/FormatV2.php | 5 + lib/Persistence/PurgeLimiter.php | 1 - lib/Persistence/ServerSalt.php | 5 +- lib/Request.php | 13 +- tst/Bootstrap.php | 595 +++++++++++++++++++++++++++- tst/ControllerTest.php | 27 +- tst/ControllerWithGcsTest.php | 59 +++ tst/Data/DatabaseTest.php | 42 ++ tst/Data/FilesystemTest.php | 1 + tst/Data/GoogleCloudStorageTest.php | 585 +-------------------------- tst/ModelTest.php | 154 +++++++ 14 files changed, 949 insertions(+), 660 deletions(-) create mode 100644 tst/ControllerWithGcsTest.php diff --git a/lib/Controller.php b/lib/Controller.php index 4a1aa0d..fb919ca 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -195,22 +195,17 @@ class Controller */ private function _create() { - try { - // Ensure last paste from visitors IP address was more than configured amount of seconds ago. - ServerSalt::setStore($this->_model->getStore()); - TrafficLimiter::setConfiguration($this->_conf); - TrafficLimiter::setStore($this->_model->getStore()); - if (!TrafficLimiter::canPass()) { - $this->_return_message( - 1, I18n::_( - '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; } diff --git a/lib/Data/Database.php b/lib/Data/Database.php index ae4da19..a15b862 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -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; + } } /** @@ -348,19 +352,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,11 +424,15 @@ 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; + } } /** diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 6bbea60..2066769 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -34,12 +34,9 @@ class GoogleCloudStorage extends AbstractData if (is_array($options) && array_key_exists('prefix', $options)) { $prefix = $options['prefix']; } - if (is_array($options) && array_key_exists('client', $options)) { - $client = $options['client']; - } if (!(self::$_instance instanceof self)) { - self::$_instance = new self($bucket, $prefix, $client); + self::$_instance = new self($bucket, $prefix); } return self::$_instance; } @@ -48,16 +45,12 @@ class GoogleCloudStorage extends AbstractData protected $_bucket = null; protected $_prefix = 'pastes'; - public function __construct($bucket, $prefix, $client = null) + public function __construct($bucket, $prefix) { parent::__construct(); - if ($client == null) { - $this->_client = new StorageClient(array('suppressKeyFileNotice' => true)); - } else { - // use given client for test purposes - $this->_client = $client; - } - + $this->_client = class_exists('StorageClientStub', false) ? + new \StorageClientStub(array()) : + new StorageClient(array('suppressKeyFileNotice' => true)); $this->_bucket = $this->_client->bucket($bucket); if ($prefix != null) { $this->_prefix = $prefix; diff --git a/lib/FormatV2.php b/lib/FormatV2.php index a06aa5d..d2055f3 100644 --- a/lib/FormatV2.php +++ b/lib/FormatV2.php @@ -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/Persistence/PurgeLimiter.php b/lib/Persistence/PurgeLimiter.php index 6b60817..89d5d60 100644 --- a/lib/Persistence/PurgeLimiter.php +++ b/lib/Persistence/PurgeLimiter.php @@ -59,7 +59,6 @@ class PurgeLimiter extends AbstractPersistence * * @access public * @static - * @throws \Exception * @return bool */ public static function canPurge() diff --git a/lib/Persistence/ServerSalt.php b/lib/Persistence/ServerSalt.php index 93f5486..14f7bd1 100644 --- a/lib/Persistence/ServerSalt.php +++ b/lib/Persistence/ServerSalt.php @@ -12,7 +12,6 @@ namespace PrivateBin\Persistence; -use Exception; use PrivateBin\Data\AbstractData; /** @@ -54,8 +53,7 @@ class ServerSalt extends AbstractPersistence */ public static function generate() { - $randomSalt = bin2hex(random_bytes(256)); - return $randomSalt; + return bin2hex(random_bytes(256)); } /** @@ -63,7 +61,6 @@ class ServerSalt extends AbstractPersistence * * @access public * @static - * @throws Exception * @return string */ public static function get() diff --git a/lib/Request.php b/lib/Request.php index 5776cab..df517bb 100644 --- a/lib/Request.php +++ b/lib/Request.php @@ -108,6 +108,8 @@ class Request case 'DELETE': case 'PUT': case 'POST': + // it might be a creation or a deletion, the latter is detected below + $this->_operation = 'create'; $this->_params = Json::decode( file_get_contents(self::$_inputStream) ); @@ -125,15 +127,10 @@ class Request } // prepare operation, depending on current parameters - if ( - array_key_exists('ct', $this->_params) && - !empty($this->_params['ct']) - ) { - $this->_operation = 'create'; - } elseif (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid'])) { + if (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid'])) { if (array_key_exists('deletetoken', $this->_params) && !empty($this->_params['deletetoken'])) { $this->_operation = 'delete'; - } else { + } else if ($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; diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index b539351..bdf8a38 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -1,5 +1,14 @@ _config = $config; + $this->_connection = new ConnectionInterfaceStub(); + } + + public function bucket($name, $userProject = false) + { + if (!key_exists($name, $this->_buckets)) { + $b = new BucketStub($this->_connection, $name, array(), $this); + $this->_buckets[$name] = $b; + } + return $this->_buckets[$name]; + } + + /** + * @throws \Google\Cloud\Core\Exception\NotFoundException + */ + public function deleteBucket($name) + { + if (key_exists($name, $this->_buckets)) { + unset($this->_buckets[$name]); + } else { + throw new NotFoundException(); + } + } + + public function buckets(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function registerStreamWrapper($protocol = null) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function unregisterStreamWrapper($protocol = null) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function signedUrlUploader($uri, $data, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function timestamp(\DateTimeInterface $timestamp, $nanoSeconds = null) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getServiceAccount(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function hmacKeys(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function hmacKey($accessId, $projectId = null, array $metadata = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function createHmacKey($serviceAccountEmail, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function createBucket($name, array $options = array()) + { + if (key_exists($name, $this->_buckets)) { + throw new BadRequestException('already exists'); + } + $b = new BucketStub($this->_connection, $name, array(), $this); + $this->_buckets[$name] = $b; + return $b; + } +} + +/** + * Class BucketStub stubs a GCS bucket. + */ +class BucketStub extends Bucket +{ + public $_objects; + private $_name; + private $_info; + private $_connection; + private $_client; + + public function __construct(ConnectionInterface $connection, $name, array $info = array(), $client = null) + { + $this->_name = $name; + $this->_info = $info; + $this->_connection = $connection; + $this->_objects = array(); + $this->_client = $client; + } + + public function acl() + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function defaultAcl() + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function exists() + { + return true; + } + + public function upload($data, array $options = array()) + { + if (!is_string($data) || !key_exists('name', $options)) { + throw new BadMethodCallException('not supported by this stub'); + } + + $name = $options['name']; + $generation = '1'; + $o = new StorageObjectStub($this->_connection, $name, $this, $generation, $options); + $this->_objects[$options['name']] = $o; + $o->setData($data); + } + + public function uploadAsync($data, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getResumableUploader($data, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getStreamableUploader($data, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function object($name, array $options = array()) + { + if (key_exists($name, $this->_objects)) { + return $this->_objects[$name]; + } else { + return new StorageObjectStub($this->_connection, $name, $this, null, $options); + } + } + + public function objects(array $options = array()) + { + $prefix = key_exists('prefix', $options) ? $options['prefix'] : ''; + + return new CallbackFilterIterator( + new ArrayIterator($this->_objects), + function ($current, $key, $iterator) use ($prefix) { + return substr($key, 0, strlen($prefix)) == $prefix; + } + ); + } + + public function createNotification($topic, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function notification($id) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function notifications(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function delete(array $options = array()) + { + $this->_client->deleteBucket($this->_name); + } + + public function update(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function compose(array $sourceObjects, $name, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function info(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function reload(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function name() + { + return $this->_name; + } + + public static function lifecycle(array $lifecycle = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function currentLifecycle(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function isWritable($file = null) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function iam() + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function lockRetentionPolicy(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function signedUrl($expires, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function generateSignedPostPolicyV4($objectName, $expires, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } +} + +/** + * Class StorageObjectStub stubs a GCS storage object. + */ +class StorageObjectStub extends StorageObject +{ + private $_name; + private $_data; + private $_info; + private $_bucket; + private $_generation; + private $_exists = false; + private $_connection; + + public function __construct(ConnectionInterface $connection, $name, $bucket, $generation = null, array $info = array(), $encryptionKey = null, $encryptionKeySHA256 = null) + { + $this->_name = $name; + $this->_bucket = $bucket; + $this->_generation = $generation; + $this->_info = $info; + $this->_connection = $connection; + $timeCreated = new Datetime(); + $this->_info['metadata']['timeCreated'] = $timeCreated->format(GoogleCloudStorage::DATETIME_FORMAT); + } + + public function acl() + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function exists(array $options = array()) + { + return key_exists($this->_name, $this->_bucket->_objects); + } + + /** + * @throws NotFoundException + */ + public function delete(array $options = array()) + { + if (key_exists($this->_name, $this->_bucket->_objects)) { + unset($this->_bucket->_objects[$this->_name]); + } else { + throw new NotFoundException('key ' . $this->_name . ' not found.'); + } + } + + /** + * @throws NotFoundException + */ + public function update(array $metadata, array $options = array()) + { + if (!$this->_exists) { + throw new NotFoundException('key ' . $this->_name . ' not found.'); + } + $this->_info = $metadata; + } + + public function copy($destination, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function rewrite($destination, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function rename($name, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + /** + * @throws NotFoundException + */ + public function downloadAsString(array $options = array()) + { + if (!$this->_exists) { + throw new NotFoundException('key ' . $this->_name . ' not found.'); + } + return $this->_data; + } + + public function downloadToFile($path, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function downloadAsStream(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function downloadAsStreamAsync(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function signedUrl($expires, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function signedUploadUrl($expires, array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function beginSignedUploadSession(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function info(array $options = array()) + { + return key_exists('metadata',$this->_info) ? $this->_info['metadata'] : array(); + } + + public function reload(array $options = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function name() + { + return $this->_name; + } + + public function identity() + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function gcsUri() + { + return sprintf( + 'gs://%s/%s', + $this->_bucket->name(), + $this->_name + ); + } + + public function setData($data) + { + $this->_data = $data; + $this->_exists = true; + } +} + +/** + * Class ConnectionInterfaceStub required for the stubs. + */ +class ConnectionInterfaceStub implements ConnectionInterface +{ + public function deleteAcl(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getAcl(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function listAcl(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function insertAcl(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function patchAcl(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function deleteBucket(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getBucket(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function listBuckets(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function insertBucket(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getBucketIamPolicy(array $args) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function setBucketIamPolicy(array $args) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function testBucketIamPermissions(array $args) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function patchBucket(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function deleteObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function copyObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function rewriteObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function composeObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function listObjects(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function patchObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function downloadObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function insertObject(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getNotification(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function deleteNotification(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function insertNotification(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function listNotifications(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getServiceAccount(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function lockRetentionPolicy(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function createHmacKey(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function deleteHmacKey(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function getHmacKey(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function updateHmacKey(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } + + public function listHmacKeys(array $args = array()) + { + throw new BadMethodCallException('not supported by this stub'); + } +} + +/** + * Class Helper provides unit tests pastes and comments of various formats + */ class Helper { /** @@ -155,7 +744,11 @@ class Helper public static function getPastePost($version = 2, array $meta = array()) { $example = self::getPaste($version, $meta); - $example['meta'] = array('expire' => $example['meta']['expire']); + if ($version == 2) { + $example['meta'] = array('expire' => $example['meta']['expire']); + } else { + unset($example['meta']['postdate']); + } return $example; } diff --git a/tst/ControllerTest.php b/tst/ControllerTest.php index 0aa7d79..5c95127 100644 --- a/tst/ControllerTest.php +++ b/tst/ControllerTest.php @@ -48,6 +48,8 @@ class ControllerTest extends PHPUnit_Framework_TestCase */ public function testView() { + $_SERVER['QUERY_STRING'] = Helper::getPasteId(); + $_GET[Helper::getPasteId()] = ''; ob_start(); new Controller; $content = ob_get_contents(); @@ -470,6 +472,29 @@ class ControllerTest extends PHPUnit_Framework_TestCase $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data'); } + /** + * @runInSeparateProcess + */ + public function testCreateInvalidFormat() + { + $options = parse_ini_file(CONF, true); + $options['traffic']['limit'] = 0; + Helper::createIniFile(CONF, $options); + $file = tempnam(sys_get_temp_dir(), 'FOO'); + file_put_contents($file, Helper::getPasteJson(1)); + Request::setInputStream($file); + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest'; + $_SERVER['REQUEST_METHOD'] = 'POST'; + $_SERVER['REMOTE_ADDR'] = '::1'; + ob_start(); + new Controller; + $content = ob_get_contents(); + ob_end_clean(); + $response = json_decode($content, true); + $this->assertEquals(1, $response['status'], 'outputs error status'); + $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data'); + } + /** * @runInSeparateProcess */ @@ -518,7 +543,7 @@ class ControllerTest extends PHPUnit_Framework_TestCase ob_end_clean(); $response = json_decode($content, true); $this->assertEquals(1, $response['status'], 'outputs error status'); - $this->assertFalse($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'paste exists after posting data'); + $this->assertFalse($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment exists after posting data'); } /** diff --git a/tst/ControllerWithGcsTest.php b/tst/ControllerWithGcsTest.php new file mode 100644 index 0000000..3983341 --- /dev/null +++ b/tst/ControllerWithGcsTest.php @@ -0,0 +1,59 @@ +false)); + $handler = HttpHandlerFactory::build($httpClient); + + $name = 'pb-'; + $alphabet = 'abcdefghijklmnopqrstuvwxyz'; + for ($i = 0; $i < 29; ++$i) { + $name .= $alphabet[rand(0, strlen($alphabet) - 1)]; + } + self::$_client = new StorageClientStub(array()); + self::$_bucket = self::$_client->createBucket($name); + } + + public function setUp() + { + /* Setup Routine */ + $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; + if (!is_dir($this->_path)) { + mkdir($this->_path); + } + $this->_options = array( + 'bucket' => self::$_bucket->name(), + 'prefix' => 'pastes', + ); + $this->_data = GoogleCloudStorage::getInstance($this->_options); + ServerSalt::setStore($this->_data); + TrafficLimiter::setStore($this->_data); + $this->reset(); + } + + public function reset() + { + parent::reset(); + // but then inject a db config + $options = parse_ini_file(CONF, true); + $options['model'] = array( + 'class' => 'GoogleCloudStorage', + ); + $options['model_options'] = $this->_options; + Helper::createIniFile(CONF, $options); + } +} diff --git a/tst/Data/DatabaseTest.php b/tst/Data/DatabaseTest.php index f9cd265..1cfc0be 100644 --- a/tst/Data/DatabaseTest.php +++ b/tst/Data/DatabaseTest.php @@ -302,6 +302,48 @@ class DatabaseTest extends PHPUnit_Framework_TestCase Helper::rmDir($this->_path); } + public function testCorruptMeta() + { + mkdir($this->_path); + $path = $this->_path . DIRECTORY_SEPARATOR . 'meta-test.sq3'; + if (is_file($path)) { + unlink($path); + } + $this->_options['dsn'] = 'sqlite:' . $path; + $this->_options['tbl'] = 'baz_'; + $model = Database::getInstance($this->_options); + $paste = Helper::getPaste(1, array('expire_date' => 1344803344)); + unset($paste['meta']['formatter'], $paste['meta']['opendiscussion'], $paste['meta']['salt']); + $model->delete(Helper::getPasteId()); + + $db = new PDO( + $this->_options['dsn'], + $this->_options['usr'], + $this->_options['pwd'], + $this->_options['opt'] + ); + $statement = $db->prepare('INSERT INTO baz_paste VALUES(?,?,?,?,?,?,?,?,?)'); + $statement->execute( + array( + Helper::getPasteId(), + $paste['data'], + $paste['meta']['postdate'], + $paste['meta']['expire_date'], + 0, + 0, + '{', + null, + null, + ) + ); + $statement->closeCursor(); + + $this->assertTrue($model->exists(Helper::getPasteId()), 'paste exists after storing it'); + $this->assertEquals($paste, $model->read(Helper::getPasteId())); + + Helper::rmDir($this->_path); + } + public function testTableUpgrade() { mkdir($this->_path); diff --git a/tst/Data/FilesystemTest.php b/tst/Data/FilesystemTest.php index 37e03f3..684c294 100644 --- a/tst/Data/FilesystemTest.php +++ b/tst/Data/FilesystemTest.php @@ -117,6 +117,7 @@ class FilesystemTest extends PHPUnit_Framework_TestCase $this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does not yet exist'); $this->assertFalse($this->_model->create(Helper::getPasteId(), $paste), 'unable to store broken paste'); $this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste does still not exist'); + $this->assertFalse($this->_model->setValue('foo', 'non existing namespace'), 'rejects setting value in non existing namespace'); } public function testCommentErrorDetection() diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 7945e2b..557984e 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -1,12 +1,6 @@ _model = GoogleCloudStorage::getInstance(array( 'bucket' => self::$_bucket->name(), 'prefix' => 'pastes', - 'client' => self::$_client, )); + )); } public function tearDown() @@ -201,580 +195,3 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $this->assertFalse($this->_model->getValue('traffic_limiter', $client)); } } - -/** - * Class StorageClientStub provides a limited stub for performing the unit test - */ -class StorageClientStub extends StorageClient -{ - private $_config = null; - private $_connection = null; - private $_buckets = array(); - - public function __construct(array $config = array()) - { - $this->_config = $config; - $this->_connection = new ConnectionInterfaceStub(); - } - - public function bucket($name, $userProject = false) - { - if (!key_exists($name, $this->_buckets)) { - $b = new BucketStub($this->_connection, $name, array(), $this); - $this->_buckets[$name] = $b; - } - return $this->_buckets[$name]; - } - - /** - * @throws \Google\Cloud\Core\Exception\NotFoundException - */ - public function deleteBucket($name) - { - if (key_exists($name, $this->_buckets)) { - unset($this->_buckets[$name]); - } else { - throw new NotFoundException(); - } - } - - public function buckets(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function registerStreamWrapper($protocol = null) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function unregisterStreamWrapper($protocol = null) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function signedUrlUploader($uri, $data, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function timestamp(\DateTimeInterface $timestamp, $nanoSeconds = null) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getServiceAccount(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function hmacKeys(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function hmacKey($accessId, $projectId = null, array $metadata = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function createHmacKey($serviceAccountEmail, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function createBucket($name, array $options = array()) - { - if (key_exists($name, $this->_buckets)) { - throw new BadRequestException('already exists'); - } - $b = new BucketStub($this->_connection, $name, array(), $this); - $this->_buckets[$name] = $b; - return $b; - } -} - -/** - * Class BucketStub stubs a GCS bucket. - */ -class BucketStub extends Bucket -{ - public $_objects; - private $_name; - private $_info; - private $_connection; - private $_client; - - public function __construct(ConnectionInterface $connection, $name, array $info = array(), $client = null) - { - $this->_name = $name; - $this->_info = $info; - $this->_connection = $connection; - $this->_objects = array(); - $this->_client = $client; - } - - public function acl() - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function defaultAcl() - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function exists() - { - return true; - } - - public function upload($data, array $options = array()) - { - if (!is_string($data) || !key_exists('name', $options)) { - throw new BadMethodCallException('not supported by this stub'); - } - - $name = $options['name']; - $generation = '1'; - $o = new StorageObjectStub($this->_connection, $name, $this, $generation, $options); - $this->_objects[$options['name']] = $o; - $o->setData($data); - } - - public function uploadAsync($data, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getResumableUploader($data, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getStreamableUploader($data, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function object($name, array $options = array()) - { - if (key_exists($name, $this->_objects)) { - return $this->_objects[$name]; - } else { - return new StorageObjectStub($this->_connection, $name, $this, null, $options); - } - } - - public function objects(array $options = array()) - { - $prefix = key_exists('prefix', $options) ? $options['prefix'] : ''; - - return new CallbackFilterIterator( - new ArrayIterator($this->_objects), - function ($current, $key, $iterator) use ($prefix) { - return substr($key, 0, strlen($prefix)) == $prefix; - } - ); - } - - public function createNotification($topic, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function notification($id) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function notifications(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function delete(array $options = array()) - { - $this->_client->deleteBucket($this->_name); - } - - public function update(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function compose(array $sourceObjects, $name, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function info(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function reload(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function name() - { - return $this->_name; - } - - public static function lifecycle(array $lifecycle = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function currentLifecycle(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function isWritable($file = null) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function iam() - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function lockRetentionPolicy(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function signedUrl($expires, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function generateSignedPostPolicyV4($objectName, $expires, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } -} - -/** - * Class StorageObjectStub stubs a GCS storage object. - */ -class StorageObjectStub extends StorageObject -{ - private $_name; - private $_data; - private $_info; - private $_bucket; - private $_generation; - private $_exists = false; - private $_connection; - - public function __construct(ConnectionInterface $connection, $name, $bucket, $generation = null, array $info = array(), $encryptionKey = null, $encryptionKeySHA256 = null) - { - $this->_name = $name; - $this->_bucket = $bucket; - $this->_generation = $generation; - $this->_info = $info; - $this->_connection = $connection; - $timeCreated = new Datetime(); - $this->_info['metadata']['timeCreated'] = $timeCreated->format(GoogleCloudStorage::DATETIME_FORMAT); - } - - public function acl() - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function exists(array $options = array()) - { - return key_exists($this->_name, $this->_bucket->_objects); - } - - /** - * @throws NotFoundException - */ - public function delete(array $options = array()) - { - if (key_exists($this->_name, $this->_bucket->_objects)) { - unset($this->_bucket->_objects[$this->_name]); - } else { - throw new NotFoundException('key ' . $this->_name . ' not found.'); - } - } - - /** - * @throws NotFoundException - */ - public function update(array $metadata, array $options = array()) - { - if (!$this->_exists) { - throw new NotFoundException('key ' . $this->_name . ' not found.'); - } - $this->_info = $metadata; - } - - public function copy($destination, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function rewrite($destination, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function rename($name, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - /** - * @throws NotFoundException - */ - public function downloadAsString(array $options = array()) - { - if (!$this->_exists) { - throw new NotFoundException('key ' . $this->_name . ' not found.'); - } - return $this->_data; - } - - public function downloadToFile($path, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function downloadAsStream(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function downloadAsStreamAsync(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function signedUrl($expires, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function signedUploadUrl($expires, array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function beginSignedUploadSession(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function info(array $options = array()) - { - return key_exists('metadata',$this->_info) ? $this->_info['metadata'] : array(); - } - - public function reload(array $options = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function name() - { - return $this->_name; - } - - public function identity() - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function gcsUri() - { - return sprintf( - 'gs://%s/%s', - $this->_bucket->name(), - $this->_name - ); - } - - public function setData($data) - { - $this->_data = $data; - $this->_exists = true; - } -} - -/** - * Class ConnectionInterfaceStub required for the stubs. - */ -class ConnectionInterfaceStub implements ConnectionInterface -{ - public function deleteAcl(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getAcl(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function listAcl(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function insertAcl(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function patchAcl(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function deleteBucket(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getBucket(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function listBuckets(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function insertBucket(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getBucketIamPolicy(array $args) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function setBucketIamPolicy(array $args) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function testBucketIamPermissions(array $args) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function patchBucket(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function deleteObject(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function copyObject(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function rewriteObject(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function composeObject(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getObject(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function listObjects(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function patchObject(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function downloadObject(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function insertObject(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getNotification(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function deleteNotification(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function insertNotification(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function listNotifications(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getServiceAccount(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function lockRetentionPolicy(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function createHmacKey(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function deleteHmacKey(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function getHmacKey(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function updateHmacKey(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } - - public function listHmacKeys(array $args = array()) - { - throw new BadMethodCallException('not supported by this stub'); - } -} diff --git a/tst/ModelTest.php b/tst/ModelTest.php index 478d4c1..fb82442 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -102,6 +102,58 @@ class ModelTest extends PHPUnit_Framework_TestCase $this->assertEquals(array(), $paste->getComments(), 'comment was deleted with paste'); } + public function testPasteV1() + { + $pasteData = Helper::getPaste(1); + unset($pasteData['meta']['formatter']); + + $path = $this->_path . DIRECTORY_SEPARATOR . 'v1-test.sq3'; + if (is_file($path)) { + unlink($path); + } + $options = parse_ini_file(CONF_SAMPLE, true); + $options['purge']['limit'] = 0; + $options['model'] = array( + 'class' => 'Database', + ); + $options['model_options'] = array( + 'dsn' => 'sqlite:' . $path, + 'usr' => null, + 'pwd' => null, + 'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION), + ); + Helper::createIniFile(CONF, $options); + $model = new Model(new Configuration); + $model->getPaste('0000000000000000')->exists(); // triggers database table creation + $model->getPaste(Helper::getPasteId())->delete(); // deletes the cache + + $db = new PDO( + $options['model_options']['dsn'], + $options['model_options']['usr'], + $options['model_options']['pwd'], + $options['model_options']['opt'] + ); + $statement = $db->prepare('INSERT INTO paste VALUES(?,?,?,?,?,?,?,?,?)'); + $statement->execute( + array( + Helper::getPasteId(), + $pasteData['data'], + $pasteData['meta']['postdate'], + 0, + 0, + 0, + json_encode($pasteData['meta']), + null, + null, + ) + ); + $statement->closeCursor(); + + $paste = $model->getPaste(Helper::getPasteId()); + $paste->getDeleteToken(); + $this->assertEquals('plaintext', $paste->get()['meta']['formatter'], 'paste got created with default formatter'); + } + public function testCommentDefaults() { $comment = new Comment( @@ -133,6 +185,96 @@ class ModelTest extends PHPUnit_Framework_TestCase $paste->store(); } + /** + * @expectedException Exception + * @expectedExceptionCode 76 + */ + public function testStoreFail() + { + $path = $this->_path . DIRECTORY_SEPARATOR . 'model-store-test.sq3'; + if (is_file($path)) { + unlink($path); + } + $options = parse_ini_file(CONF_SAMPLE, true); + $options['purge']['limit'] = 0; + $options['model'] = array( + 'class' => 'Database', + ); + $options['model_options'] = array( + 'dsn' => 'sqlite:' . $path, + 'usr' => null, + 'pwd' => null, + 'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION), + ); + Helper::createIniFile(CONF, $options); + $model = new Model(new Configuration); + + $pasteData = Helper::getPastePost(); + $model->getPaste(Helper::getPasteId())->delete(); + $model->getPaste(Helper::getPasteId())->exists(); + + $db = new PDO( + $options['model_options']['dsn'], + $options['model_options']['usr'], + $options['model_options']['pwd'], + $options['model_options']['opt'] + ); + $statement = $db->prepare('DROP TABLE paste'); + $statement->execute(); + $statement->closeCursor(); + + $paste = $model->getPaste(); + $paste->setData($pasteData); + $paste->store(); + } + + /** + * @expectedException Exception + * @expectedExceptionCode 70 + */ + public function testCommentStoreFail() + { + $path = $this->_path . DIRECTORY_SEPARATOR . 'model-test.sq3'; + if (is_file($path)) { + unlink($path); + } + $options = parse_ini_file(CONF_SAMPLE, true); + $options['purge']['limit'] = 0; + $options['model'] = array( + 'class' => 'Database', + ); + $options['model_options'] = array( + 'dsn' => 'sqlite:' . $path, + 'usr' => null, + 'pwd' => null, + 'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION), + ); + Helper::createIniFile(CONF, $options); + $model = new Model(new Configuration); + + $pasteData = Helper::getPastePost(); + $commentData = Helper::getCommentPost(); + $model->getPaste(Helper::getPasteId())->delete(); + + $paste = $model->getPaste(); + $paste->setData($pasteData); + $paste->store(); + + $db = new PDO( + $options['model_options']['dsn'], + $options['model_options']['usr'], + $options['model_options']['pwd'], + $options['model_options']['opt'] + ); + $statement = $db->prepare('DROP TABLE comment'); + $statement->execute(); + $statement->closeCursor(); + + $comment = $paste->getComment(Helper::getPasteId()); + $comment->setData($commentData); + $comment->store(); + } + /** * @expectedException Exception * @expectedExceptionCode 69 @@ -195,6 +337,18 @@ class ModelTest extends PHPUnit_Framework_TestCase $paste->get(); } + /** + * @expectedException Exception + * @expectedExceptionCode 75 + */ + public function testInvalidPasteFormat() + { + $pasteData = Helper::getPastePost(); + $pasteData['adata'][1] = 'format does not exist'; + $paste = $this->_model->getPaste(); + $paste->setData($pasteData); + } + /** * @expectedException Exception * @expectedExceptionCode 60 From 1f2dddd9d85c3feb0dd4c210a41bc369252de3c1 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 13 Jun 2021 10:53:01 +0200 Subject: [PATCH 135/478] address Codacy issues --- lib/Data/AbstractData.php | 8 ++++---- lib/Data/Database.php | 12 ++++++------ lib/Data/Filesystem.php | 10 +++++----- lib/Persistence/ServerSalt.php | 9 --------- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index 6f2dee5..52a85a4 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -35,7 +35,7 @@ abstract class AbstractData * @static * @var array */ - protected static $_traffic_limiter_cache = array(); + protected static $_last_cache = array(); /** * Enforce singleton, disable constructor @@ -150,9 +150,9 @@ abstract class AbstractData public function purgeValues($namespace, $time) { if ($namespace === 'traffic_limiter') { - foreach (self::$_traffic_limiter_cache as $key => $last_access) { - if ($last_access <= $time) { - unset(self::$_traffic_limiter_cache[$key]); + foreach (self::$_last_cache as $key => $last_submission) { + if ($last_submission <= $time) { + unset(self::$_last_cache[$key]); } } } diff --git a/lib/Data/Database.php b/lib/Data/Database.php index a15b862..dea1be6 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -447,9 +447,9 @@ class Database extends AbstractData public function setValue($value, $namespace, $key = '') { if ($namespace === 'traffic_limiter') { - self::$_traffic_limiter_cache[$key] = $value; + self::$_last_cache[$key] = $value; try { - $value = Json::encode(self::$_traffic_limiter_cache); + $value = Json::encode(self::$_last_cache); } catch (Exception $e) { return false; } @@ -492,12 +492,12 @@ class Database extends AbstractData } if ($value && $namespace === 'traffic_limiter') { try { - self::$_traffic_limiter_cache = Json::decode($value); + self::$_last_cache = Json::decode($value); } catch (Exception $e) { - self::$_traffic_limiter_cache = array(); + self::$_last_cache = array(); } - if (array_key_exists($key, self::$_traffic_limiter_cache)) { - return self::$_traffic_limiter_cache[$key]; + if (array_key_exists($key, self::$_last_cache)) { + return self::$_last_cache[$key]; } } return (string) $value; diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 384c72d..9b8e7ba 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -263,10 +263,10 @@ class Filesystem extends AbstractData ' Date: Sun, 13 Jun 2021 11:02:53 +0200 Subject: [PATCH 136/478] address Scrutinizer issues --- lib/Data/GoogleCloudStorage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 2066769..d678db3 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -224,7 +224,7 @@ class GoogleCloudStorage extends AbstractData if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') { continue; } - $info = $object->info(); + $info = $object->info(); if (key_exists('metadata', $info) && key_exists('value', $info['metadata'])) { $value = $info['metadata']['value']; if (is_numeric($value) && intval($value) < $time) { @@ -293,7 +293,7 @@ class GoogleCloudStorage extends AbstractData $data = $o->downloadAsString(); return Json::decode($data); } catch (NotFoundException $e) { - return false; + return ''; } } From bbcf57de0ef1d1ae9b4a9281f630b25712f3b129 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 13 Jun 2021 11:08:38 +0200 Subject: [PATCH 137/478] address Scrutinizer issues --- tst/Data/GoogleCloudStorageTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 557984e..2489743 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -139,7 +139,7 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $storedSalt = $this->_model->getValue('salt', ''); $this->assertEquals($salt, $storedSalt); $this->_model->purgeValues('salt', time() + 60); - $this->assertFalse($this->_model->getValue('salt', 'master')); + $this->assertEquals('', $this->_model->getValue('salt', 'master')); $client = hash_hmac('sha512', '127.0.0.1', $salt); $expire = time(); @@ -150,14 +150,14 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $this->_model->purgeValues('traffic_limiter', time() - 60); $this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client)); $this->_model->purgeValues('traffic_limiter', time() + 60); - $this->assertFalse($this->_model->getValue('traffic_limiter', $client)); + $this->assertEquals('', $this->_model->getValue('traffic_limiter', $client)); $purgeAt = $expire + (15 * 60); $this->_model->setValue($purgeAt, 'purge_limiter', ''); $storedPurgedAt = $this->_model->getValue('purge_limiter', ''); $this->assertEquals($purgeAt, $storedPurgedAt); $this->_model->purgeValues('purge_limiter', time() + 60); - $this->assertFalse($this->_model->getValue('purge_limiter', 'at')); + $this->assertEquals('', $this->_model->getValue('purge_limiter', 'at')); } /** From fa4fe2852d09e39234bdf5618fb884a4c7160486 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 13 Jun 2021 11:12:19 +0200 Subject: [PATCH 138/478] address Scrutinizer issues --- tst/Data/GoogleCloudStorageTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 2489743..827297a 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -176,7 +176,7 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client)); $this->_model->purgeValues('traffic_limiter', time() + 60); - $this->assertFalse($this->_model->getValue('traffic_limiter', $client)); + $this->assertEquals('', $this->_model->getValue('traffic_limiter', $client)); } public function testKeyValuePurgeTrafficLimiterWithKey() @@ -192,6 +192,6 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client)); $this->_model->purgeValues('traffic_limiter', time() + 60); - $this->assertFalse($this->_model->getValue('traffic_limiter', $client)); + $this->assertEquals('', $this->_model->getValue('traffic_limiter', $client)); } } From 68b097087d8bb6af219967031447859019d8af56 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 13 Jun 2021 11:16:29 +0200 Subject: [PATCH 139/478] apply StyleCI recommendation --- lib/Request.php | 4 ++-- tst/Bootstrap.php | 2 -- tst/ModelTest.php | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/Request.php b/lib/Request.php index df517bb..858f131 100644 --- a/lib/Request.php +++ b/lib/Request.php @@ -110,7 +110,7 @@ class Request case 'POST': // it might be a creation or a deletion, the latter is detected below $this->_operation = 'create'; - $this->_params = Json::decode( + $this->_params = Json::decode( file_get_contents(self::$_inputStream) ); break; @@ -130,7 +130,7 @@ class Request 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 if ($this->_operation != 'create') { + } elseif ($this->_operation != 'create') { $this->_operation = 'read'; } } elseif (array_key_exists('jsonld', $this->_params) && !empty($this->_params['jsonld'])) { diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index bdf8a38..97f9405 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -1,13 +1,11 @@ _model->getPaste(); + $paste = $this->_model->getPaste(); $paste->setData($pasteData); } From 078c5785ddf09dfaac3c3c01e7848664685a69ee Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 13 Jun 2021 12:40:06 +0200 Subject: [PATCH 140/478] fix unit tests on php < 7.3 --- lib/Data/Database.php | 26 ++++++++++++++++---------- tst/ModelTest.php | 1 + 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index dea1be6..1d1327f 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -233,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; } @@ -643,15 +646,18 @@ 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 - ); + 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/tst/ModelTest.php b/tst/ModelTest.php index 327e701..9432124 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -259,6 +259,7 @@ class ModelTest extends PHPUnit_Framework_TestCase $paste = $model->getPaste(); $paste->setData($pasteData); $paste->store(); + $paste->exists(); $db = new PDO( $options['model_options']['dsn'], From d0248d55d356914479e670c76342b9daaa535634 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 13 Jun 2021 12:43:18 +0200 Subject: [PATCH 141/478] address Scrutinizer issues --- lib/Data/GoogleCloudStorage.php | 2 +- lib/Json.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index d678db3..c7dfcb9 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -291,7 +291,7 @@ class GoogleCloudStorage extends AbstractData try { $o = $this->_bucket->object($key); $data = $o->downloadAsString(); - return Json::decode($data); + return (string) Json::decode($data); } catch (NotFoundException $e) { return ''; } diff --git a/lib/Json.php b/lib/Json.php index b6567ed..5f4efcf 100644 --- a/lib/Json.php +++ b/lib/Json.php @@ -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; } /** From 9357f122b7c72bbf27ed0d018355b8d762decb97 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 13 Jun 2021 12:49:59 +0200 Subject: [PATCH 142/478] address Scrutinizer issues --- lib/Data/GoogleCloudStorage.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index c7dfcb9..3f5c8e2 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -21,7 +21,6 @@ class GoogleCloudStorage extends AbstractData */ public static function getInstance(array $options) { - $client = null; $bucket = null; $prefix = 'pastes'; From b4c75b541ba37896896d0b01d2ecdf4bb4b036ca Mon Sep 17 00:00:00 2001 From: Mark van Holsteijn Date: Sun, 13 Jun 2021 21:16:30 +0200 Subject: [PATCH 143/478] removed json encoding from get/setValue --- lib/Data/GoogleCloudStorage.php | 9 +++----- tst/Data/GoogleCloudStorageTest.php | 33 ++++++++--------------------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 3f5c8e2..1c26a14 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -253,14 +253,12 @@ class GoogleCloudStorage extends AbstractData $key = 'config/' . $namespace . '/' . $key; } - $data = Json::encode($value); - $metadata = array('namespace' => $namespace); if ($namespace != 'salt') { $metadata['value'] = strval($value); } try { - $this->_bucket->upload($data, array( + $this->_bucket->upload($value, array( 'name' => $key, 'chunkSize' => 262144, 'predefinedAcl' => 'private', @@ -288,9 +286,8 @@ class GoogleCloudStorage extends AbstractData $key = 'config/' . $namespace . '/' . $key; } try { - $o = $this->_bucket->object($key); - $data = $o->downloadAsString(); - return (string) Json::decode($data); + $o = $this->_bucket->object($key); + return $o->downloadAsString(); } catch (NotFoundException $e) { return ''; } diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 827297a..3b101c4 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -143,20 +143,21 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $client = hash_hmac('sha512', '127.0.0.1', $salt); $expire = time(); - $this->_model->setValue($expire, 'traffic_limiter', $client); + $this->_model->setValue(strval($expire), 'traffic_limiter', $client); $storedExpired = $this->_model->getValue('traffic_limiter', $client); - $this->assertEquals($expire, $storedExpired); - $this->assertEquals($expire, $storedExpired); + $this->assertEquals(strval($expire), $storedExpired); + $this->_model->purgeValues('traffic_limiter', time() - 60); $this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client)); $this->_model->purgeValues('traffic_limiter', time() + 60); $this->assertEquals('', $this->_model->getValue('traffic_limiter', $client)); $purgeAt = $expire + (15 * 60); - $this->_model->setValue($purgeAt, 'purge_limiter', ''); + $this->_model->setValue(strval($purgeAt), 'purge_limiter', ''); $storedPurgedAt = $this->_model->getValue('purge_limiter', ''); - $this->assertEquals($purgeAt, $storedPurgedAt); - $this->_model->purgeValues('purge_limiter', time() + 60); + $this->assertEquals(strval($purgeAt), $storedPurgedAt); + $this->_model->purgeValues('purge_limiter', $purgeAt + 60); + $this->assertEquals('', $this->_model->getValue('purge_limiter', '')); $this->assertEquals('', $this->_model->getValue('purge_limiter', 'at')); } @@ -168,25 +169,9 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase $salt = bin2hex(random_bytes(256)); $client = hash_hmac('sha512', '127.0.0.1', $salt); $expire = time(); - $this->_model->setValue($expire, 'traffic_limiter', $client); + $this->_model->setValue(strval($expire), 'traffic_limiter', $client); $storedExpired = $this->_model->getValue('traffic_limiter', $client); - $this->assertEquals($expire, $storedExpired); - - $this->_model->purgeValues('traffic_limiter', time() - 60); - $this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client)); - - $this->_model->purgeValues('traffic_limiter', time() + 60); - $this->assertEquals('', $this->_model->getValue('traffic_limiter', $client)); - } - - public function testKeyValuePurgeTrafficLimiterWithKey() - { - $salt = bin2hex(random_bytes(256)); - $client = hash_hmac('sha512', '127.0.0.1', $salt); - $expire = time(); - $this->_model->setValue($expire, 'traffic_limiter', $client); - $storedExpired = $this->_model->getValue('traffic_limiter', $client); - $this->assertEquals($expire, $storedExpired); + $this->assertEquals(strval($expire), $storedExpired); $this->_model->purgeValues('traffic_limiter', time() - 60); $this->assertEquals($storedExpired, $this->_model->getValue('traffic_limiter', $client)); From 3327645fd40bc0791b6111087b3e2b66399514ff Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 14 Jun 2021 06:44:30 +0200 Subject: [PATCH 144/478] updated doc blocks, comments, fixed indentations, moved some constant strings --- lib/Data/AbstractData.php | 6 +++--- lib/Data/Database.php | 2 +- lib/Data/Filesystem.php | 27 +++++++++++++++++++-------- lib/Data/GoogleCloudStorage.php | 2 -- tst/Bootstrap.php | 2 +- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index 52a85a4..591b91f 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -15,7 +15,7 @@ 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 { @@ -40,7 +40,7 @@ abstract class AbstractData /** * Enforce singleton, disable constructor * - * Instantiate using {@link getInstance()}, privatebin is a singleton object. + * Instantiate using {@link getInstance()}, this object implements the singleton pattern. * * @access protected */ @@ -51,7 +51,7 @@ abstract class AbstractData /** * 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 */ diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 1d1327f..0c66d33 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -234,7 +234,7 @@ class Database extends AbstractData self::$_cache[$pasteid] = false; try { - $paste = self::_select( + $paste = self::_select( 'SELECT * FROM ' . self::_sanitizeIdentifier('paste') . ' WHERE dataid = ?', array($pasteid), true ); diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 9b8e7ba..25cca45 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -23,12 +23,19 @@ use PrivateBin\Json; class Filesystem extends AbstractData { /** - * first line in file, to protect its contents + * first line in paste or comment files, to protect their contents from browsing exposed data directories * * @const string */ const PROTECTION_LINE = '_info = $info; $this->_connection = $connection; $timeCreated = new Datetime(); - $this->_info['metadata']['timeCreated'] = $timeCreated->format(GoogleCloudStorage::DATETIME_FORMAT); + $this->_info['metadata']['timeCreated'] = $timeCreated->format('Y-m-d\TH:i:s.u\Z'); } public function acl() From af54e70359ae5994abc9f505e33cea6fdccf639c Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 14 Jun 2021 06:48:46 +0200 Subject: [PATCH 145/478] apply StyleCI recommendation --- tst/Bootstrap.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index 9d9f07c..70aafdd 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -6,7 +6,6 @@ use Google\Cloud\Storage\Bucket; use Google\Cloud\Storage\Connection\ConnectionInterface; use Google\Cloud\Storage\StorageClient; use Google\Cloud\Storage\StorageObject; -use PrivateBin\Data\GoogleCloudStorage; use PrivateBin\Persistence\ServerSalt; error_reporting(E_ALL | E_STRICT); From ae1e4e3edbd583a472eb5ad683b7bab28ad596e0 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 16 Jun 2021 04:39:24 +0200 Subject: [PATCH 146/478] clarify use of getDeleteToken() method in unit test --- tst/ModelTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/ModelTest.php b/tst/ModelTest.php index 9432124..a88e029 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -150,7 +150,7 @@ class ModelTest extends PHPUnit_Framework_TestCase $statement->closeCursor(); $paste = $model->getPaste(Helper::getPasteId()); - $paste->getDeleteToken(); + $this->assertNotEmpty($paste->getDeleteToken(), 'excercise the condition to load the data from storage'); $this->assertEquals('plaintext', $paste->get()['meta']['formatter'], 'paste got created with default formatter'); } From 3d9ba10fcb10d0222956110dd9f170a80fff1a7e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 16 Jun 2021 05:19:45 +0200 Subject: [PATCH 147/478] more consistent AbstractData implementation --- lib/Data/GoogleCloudStorage.php | 112 +++++++++++++++++++------------- 1 file changed, 66 insertions(+), 46 deletions(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index d6189e1..cc4a4c6 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -9,6 +9,33 @@ use PrivateBin\Json; class GoogleCloudStorage extends AbstractData { + /** + * GCS client + * + * @access private + * @static + * @var StorageClient + */ + private static $_client = null; + + /** + * GCS bucket + * + * @access private + * @static + * @var Bucket + */ + private static $_bucket = null; + + /** + * object prefix + * + * @access private + * @static + * @var string + */ + private static $_prefix = 'pastes'; + /** * returns a Google Cloud Storage data backend. * @@ -19,9 +46,12 @@ class GoogleCloudStorage extends AbstractData */ public static function getInstance(array $options) { - $bucket = null; - $prefix = 'pastes'; + // if needed initialize the singleton + if (!(self::$_instance instanceof self)) { + self::$_instance = new self; + } + $bucket = null; if (getenv('PRIVATEBIN_GCS_BUCKET')) { $bucket = getenv('PRIVATEBIN_GCS_BUCKET'); } @@ -29,46 +59,36 @@ class GoogleCloudStorage extends AbstractData $bucket = $options['bucket']; } if (is_array($options) && array_key_exists('prefix', $options)) { - $prefix = $options['prefix']; + self::$_prefix = $options['prefix']; } - if (!(self::$_instance instanceof self)) { - self::$_instance = new self($bucket, $prefix); + if (empty(self::$_client)) { + self::$_client = class_exists('StorageClientStub', false) ? + new \StorageClientStub(array()) : + new StorageClient(array('suppressKeyFileNotice' => true)); } + self::$_bucket = self::$_client->bucket($bucket); + return self::$_instance; } - protected $_client = null; - protected $_bucket = null; - protected $_prefix = 'pastes'; - - public function __construct($bucket, $prefix) - { - parent::__construct(); - $this->_client = class_exists('StorageClientStub', false) ? - new \StorageClientStub(array()) : - new StorageClient(array('suppressKeyFileNotice' => true)); - $this->_bucket = $this->_client->bucket($bucket); - if ($prefix != null) { - $this->_prefix = $prefix; - } - } - /** - * returns the google storage object key for $pasteid in $this->_bucket. + * 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 ($this->_prefix != '') { - return $this->_prefix . '/' . $pasteid; + if (self::$_prefix != '') { + return self::$_prefix . '/' . $pasteid; } return $pasteid; } /** - * Uploads the payload in the $this->_bucket under the specified key. + * 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. @@ -77,7 +97,7 @@ class GoogleCloudStorage extends AbstractData * @param $payload array to store * @return bool true if successful, otherwise false. */ - private function upload($key, $payload) + private function _upload($key, $payload) { $metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array(); unset($metadata['attachment'], $metadata['attachmentname'], $metadata['salt']); @@ -85,7 +105,7 @@ class GoogleCloudStorage extends AbstractData $metadata[$k] = strval($v); } try { - $this->_bucket->upload(Json::encode($payload), array( + self::$_bucket->upload(Json::encode($payload), array( 'name' => $key, 'chunkSize' => 262144, 'predefinedAcl' => 'private', @@ -95,7 +115,7 @@ class GoogleCloudStorage extends AbstractData ), )); } catch (Exception $e) { - error_log('failed to upload ' . $key . ' to ' . $this->_bucket->name() . ', ' . + error_log('failed to upload ' . $key . ' to ' . self::$_bucket->name() . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); return false; } @@ -111,7 +131,7 @@ class GoogleCloudStorage extends AbstractData return false; } - return $this->upload($this->_getKey($pasteid), $paste); + return $this->_upload($this->_getKey($pasteid), $paste); } /** @@ -120,13 +140,13 @@ class GoogleCloudStorage extends AbstractData public function read($pasteid) { try { - $o = $this->_bucket->object($this->_getKey($pasteid)); + $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 ' . $this->_bucket->name() . ', ' . + error_log('failed to read ' . $pasteid . ' from ' . self::$_bucket->name() . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); return false; } @@ -140,9 +160,9 @@ class GoogleCloudStorage extends AbstractData $name = $this->_getKey($pasteid); try { - foreach ($this->_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) { + foreach (self::$_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) { try { - $this->_bucket->object($comment->name())->delete(); + self::$_bucket->object($comment->name())->delete(); } catch (NotFoundException $e) { // ignore if already deleted. } @@ -152,7 +172,7 @@ class GoogleCloudStorage extends AbstractData } try { - $this->_bucket->object($name)->delete(); + self::$_bucket->object($name)->delete(); } catch (NotFoundException $e) { // ignore if already deleted } @@ -163,7 +183,7 @@ class GoogleCloudStorage extends AbstractData */ public function exists($pasteid) { - $o = $this->_bucket->object($this->_getKey($pasteid)); + $o = self::$_bucket->object($this->_getKey($pasteid)); return $o->exists(); } @@ -176,7 +196,7 @@ class GoogleCloudStorage extends AbstractData return false; } $key = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid; - return $this->upload($key, $comment); + return $this->_upload($key, $comment); } /** @@ -187,8 +207,8 @@ class GoogleCloudStorage extends AbstractData $comments = array(); $prefix = $this->_getKey($pasteid) . '/discussion/'; try { - foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $key) { - $comment = JSON::decode($this->_bucket->object($key->name())->downloadAsString()); + 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; @@ -205,7 +225,7 @@ class GoogleCloudStorage extends AbstractData public function existsComment($pasteid, $parentid, $commentid) { $name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid; - $o = $this->_bucket->object($name); + $o = self::$_bucket->object($name); return $o->exists(); } @@ -216,7 +236,7 @@ class GoogleCloudStorage extends AbstractData { $path = 'config/' . $namespace; try { - foreach ($this->_bucket->objects(array('prefix' => $path)) as $object) { + foreach (self::$_bucket->objects(array('prefix' => $path)) as $object) { $name = $object->name(); if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') { continue; @@ -256,7 +276,7 @@ class GoogleCloudStorage extends AbstractData $metadata['value'] = strval($value); } try { - $this->_bucket->upload($value, array( + self::$_bucket->upload($value, array( 'name' => $key, 'chunkSize' => 262144, 'predefinedAcl' => 'private', @@ -266,7 +286,7 @@ class GoogleCloudStorage extends AbstractData ), )); } catch (Exception $e) { - error_log('failed to set key ' . $key . ' to ' . $this->_bucket->name() . ', ' . + error_log('failed to set key ' . $key . ' to ' . self::$_bucket->name() . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); return false; } @@ -284,7 +304,7 @@ class GoogleCloudStorage extends AbstractData $key = 'config/' . $namespace . '/' . $key; } try { - $o = $this->_bucket->object($key); + $o = self::$_bucket->object($key); return $o->downloadAsString(); } catch (NotFoundException $e) { return ''; @@ -299,12 +319,12 @@ class GoogleCloudStorage extends AbstractData $expired = array(); $now = time(); - $prefix = $this->_prefix; + $prefix = self::$_prefix; if ($prefix != '') { - $prefix = $prefix . '/'; + $prefix .= '/'; } try { - foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) { + 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']); From fd08d991fe073cce7aee254ebaf48c246c833225 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 16 Jun 2021 05:32:45 +0200 Subject: [PATCH 148/478] log errors storing persistance --- lib/Persistence/PurgeLimiter.php | 7 +++++-- lib/Persistence/ServerSalt.php | 4 +++- lib/Persistence/TrafficLimiter.php | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/Persistence/PurgeLimiter.php b/lib/Persistence/PurgeLimiter.php index 89d5d60..ef37770 100644 --- a/lib/Persistence/PurgeLimiter.php +++ b/lib/Persistence/PurgeLimiter.php @@ -73,7 +73,10 @@ class PurgeLimiter extends AbstractPersistence if ($pl + self::$_limit >= $now) { return false; } - self::$_store->setValue((string) $now, 'purge_limiter'); - return true; + $hasStored = self::$_store->setValue((string) $now, 'purge_limiter'); + if (!$hasStored) { + error_log('failed to store the purge limiter, skipping purge cycle to avoid getting stuck in a purge loop'); + } + return $hasStored; } } diff --git a/lib/Persistence/ServerSalt.php b/lib/Persistence/ServerSalt.php index 50e1cd6..1095498 100644 --- a/lib/Persistence/ServerSalt.php +++ b/lib/Persistence/ServerSalt.php @@ -65,7 +65,9 @@ class ServerSalt extends AbstractPersistence self::$_salt = $salt; } else { self::$_salt = self::generate(); - self::$_store->setValue(self::$_salt, 'salt'); + if (!self::$_store->setValue(self::$_salt, 'salt')) { + error_log('failed to store the server salt, delete tokens, traffic limiter and user icons won\'t work'); + } } return self::$_salt; } diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 4f11ec7..9e896c1 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -172,7 +172,9 @@ class TrafficLimiter extends AbstractPersistence $tl = time(); $result = true; } - self::$_store->setValue((string) $tl, 'traffic_limiter', $hash); + if (!self::$_store->setValue((string) $tl, 'traffic_limiter', $hash)) { + error_log('failed to store the traffic limiter, it probably contains outdated information'); + } return $result; } } From be164bb6a939e0293db4a869f6a3eb238fb6a1cb Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 16 Jun 2021 05:43:18 +0200 Subject: [PATCH 149/478] apply StyleCI recommendation --- lib/Data/GoogleCloudStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index cc4a4c6..9cd7f25 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -74,7 +74,7 @@ class GoogleCloudStorage extends AbstractData /** * returns the google storage object key for $pasteid in self::$_bucket. - * + * * @access private * @param $pasteid string to get the key for * @return string From 9c09018e6ed30d51b99b3647cae2905c1ab391da Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 16 Jun 2021 05:50:41 +0200 Subject: [PATCH 150/478] address Scrutinizer issues --- lib/Data/Filesystem.php | 9 +++++---- lib/Data/GoogleCloudStorage.php | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 25cca45..577ad34 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -99,12 +99,13 @@ class Filesystem extends AbstractData */ public function read($pasteid) { - if (!$this->exists($pasteid)) { + if ( + !$this->exists($pasteid) || + !$paste = self::_get(self::_dataid2path($pasteid) . $pasteid . '.php') + ) { return false; } - return self::upgradePreV1Format( - self::_get(self::_dataid2path($pasteid) . $pasteid . '.php') - ); + return self::upgradePreV1Format($paste); } /** diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 9cd7f25..f110230 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -23,7 +23,7 @@ class GoogleCloudStorage extends AbstractData * * @access private * @static - * @var Bucket + * @var \Google\Cloud\Storage\Bucket */ private static $_bucket = null; From 1fd998f325d72ad0273c714ad39a441a41492730 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 16 Jun 2021 05:57:26 +0200 Subject: [PATCH 151/478] address Scrutinizer issues --- lib/Data/GoogleCloudStorage.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index f110230..2e8e2c5 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -4,6 +4,7 @@ namespace PrivateBin\Data; use Exception; use Google\Cloud\Core\Exception\NotFoundException; +use Google\Cloud\Storage\Bucket; use Google\Cloud\Storage\StorageClient; use PrivateBin\Json; @@ -23,7 +24,7 @@ class GoogleCloudStorage extends AbstractData * * @access private * @static - * @var \Google\Cloud\Storage\Bucket + * @var Bucket */ private static $_bucket = null; From 5f28acf62975e3b5bd5c83d33cd22dffacfcd03f Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 8 Jul 2021 22:59:50 +0200 Subject: [PATCH 152/478] New translations en.json (Lithuanian) --- i18n/lt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/lt.json b/i18n/lt.json index bc287bc..10b62d8 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -185,5 +185,5 @@ "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": "Save paste" + "Save paste": "Įrašyti įdėjimą" } From ea663f7491f4610b96c73c4adab08555ec2e874a Mon Sep 17 00:00:00 2001 From: rugk Date: Thu, 5 Aug 2021 19:28:43 +0200 Subject: [PATCH 153/478] Clarify download instruction from GitHub releases Fixes https://github.com/PrivateBin/PrivateBin/issues/822 --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 70abf18..0e72823 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 From 18972ae0fad10afb206b6edb4a5434dea2b5bf4a Mon Sep 17 00:00:00 2001 From: El RIDO Date: Thu, 19 Aug 2021 10:18:08 +0200 Subject: [PATCH 154/478] luckily the PHP ini parser doesn't interpret this as an empty block, replacing the one defined above --- cfg/conf.sample.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index d362f3f..7194ee5 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -161,7 +161,7 @@ class = Filesystem [model_options] dir = PATH "data" -[model] +;[model] ; example of a Google Cloud Storage configuration ;class = GoogleCloudStorage ;[model_options] From eb10d4d35e7c19ee660e43238623403c8d6576e0 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Thu, 19 Aug 2021 10:21:21 +0200 Subject: [PATCH 155/478] be more flexible with configuration paths 1. only consider CONFIG_PATH environment variable, if non-empty 2. fall back to search in PATH (defined in index.php), if CONFIG_PATH doesn't contain a readable configuration file --- lib/Configuration.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/Configuration.php b/lib/Configuration.php index 7c4eb10..8113800 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -101,15 +101,20 @@ class Configuration */ public function __construct() { - $config = array(); - $basePath = (getenv('CONFIG_PATH') !== false ? getenv('CONFIG_PATH') : PATH . 'cfg') . DIRECTORY_SEPARATOR; - $configFile = $basePath . '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); + $config = $basePaths = array(); + $configPath = getenv('CONFIG_PATH'); + if ($configPath !== false && !empty($configPath)) { + $basePaths[] = $configPath; + } + $basePaths[] = PATH . 'cfg'; + foreach ($basePaths as $basePath) { + $configFile = $basePath . DIRECTORY_SEPARATOR . 'conf.php'; + if (is_readable($configFile)) { + $config = parse_ini_file($configFile, true); + foreach (array('main', 'model', 'model_options') as $section) { + if (!array_key_exists($section, $config)) { + throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2); + } } } } From ff3b6689581ff61cd68c752f161279183aa63150 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Thu, 19 Aug 2021 11:04:31 +0200 Subject: [PATCH 156/478] apply StyleCI recommendation --- lib/Configuration.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Configuration.php b/lib/Configuration.php index 8113800..19c2a3b 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -101,7 +101,7 @@ class Configuration */ public function __construct() { - $config = $basePaths = array(); + $config = $basePaths = array(); $configPath = getenv('CONFIG_PATH'); if ($configPath !== false && !empty($configPath)) { $basePaths[] = $configPath; @@ -116,6 +116,7 @@ class Configuration throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2); } } + break; } } From df2f5931cd2e5a57819121c54777469b31b55d7f Mon Sep 17 00:00:00 2001 From: El RIDO Date: Thu, 19 Aug 2021 19:28:52 +0200 Subject: [PATCH 157/478] improve readability, kudos @rugk --- lib/Configuration.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Configuration.php b/lib/Configuration.php index 19c2a3b..d8ef346 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -101,7 +101,8 @@ class Configuration */ public function __construct() { - $config = $basePaths = array(); + $basePaths = array(); + $config = array(); $configPath = getenv('CONFIG_PATH'); if ($configPath !== false && !empty($configPath)) { $basePaths[] = $configPath; From 3c068cd6c3e01c9fb164755dacb39472cc388ff4 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Fri, 3 Sep 2021 17:57:02 +0200 Subject: [PATCH 158/478] New translations en.json (Turkish) --- i18n/tr.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/i18n/tr.json b/i18n/tr.json index 5f3c6c0..9e28cc6 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -26,14 +26,14 @@ "%s requires a modern browser to work.": "%s requires a modern browser to work.", "New": "Yeni", "Send": "Gönder", - "Clone": "Clone", + "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)", @@ -113,10 +113,10 @@ "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", "Sending comment…": "Sending comment…", From c5c3a0e743253a2b9567561ae951471914328bbb Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Fri, 3 Sep 2021 18:58:00 +0200 Subject: [PATCH 159/478] New translations en.json (Turkish) --- i18n/tr.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/i18n/tr.json b/i18n/tr.json index 9e28cc6..0abbc37 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -118,16 +118,16 @@ "Avatar generated from IP address": "Avatar generated from IP address", "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", @@ -155,33 +155,33 @@ "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.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", From def58480b3a9fb88625dfa7b2117bc69d363ab3e Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 27 Sep 2021 10:21:34 +0200 Subject: [PATCH 160/478] New translations en.json (Occitan) --- i18n/oc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/oc.json b/i18n/oc.json index a310686..6a0a69c 100644 --- a/i18n/oc.json +++ b/i18n/oc.json @@ -184,6 +184,6 @@ "Close": "Tampar", "Encrypted note on PrivateBin": "Nòtas chifradas sus PrivateBin", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visitatz aqueste ligam per veire la nòta. Fornir lo ligam a qualqu’un mai li permet tanben d’accedir a la nòta.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Save paste": "Save paste" + "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" } From 3ba6483bf3173ca018c0147b3fc79d0bb6045464 Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 00:27:57 +0200 Subject: [PATCH 161/478] Try caching composer stuff Especially the GCM stuff may be quite large, so caching may be a good idea. I tried following https://github.com/shivammathur/setup-php#cache-composer-dependencies --- .github/workflows/tests.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 73fa11a..8504624 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,17 +20,40 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} extensions: gd, sqlite3 + + # 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.lock') }} + restore-keys: ${{ runner.os }}-composer- + + # composer - 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 From a8f7840d2548f063b53d0990e37990c30cb1587e Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 00:29:48 +0200 Subject: [PATCH 162/478] Only restore cache from current date then --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8504624..465003a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,7 +45,7 @@ jobs: with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- + restore-keys: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}- # composer - name: Setup PHPunit From 507a10adc58a4bb6f3696718af875c1ac83fe768 Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 00:32:57 +0200 Subject: [PATCH 163/478] Use composer.json instead of composer.lock In a cache --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 465003a..3c979bc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: uses: actions/cache@v2 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/composer.lock') }} + key: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}- # composer From 3f7bceb86246d2825e5376fa06f96392f4e0256c Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 00:38:21 +0200 Subject: [PATCH 164/478] Also cache PHP extensions See https://github.com/shivammathur/cache-extensions#workflow --- .github/workflows/tests.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3c979bc..271cfc5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,15 +17,33 @@ jobs: matrix: php-versions: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4'] name: PHP ${{ matrix.php-versions }} unit tests on ${{ matrix.operating-system }} + env: + extensions: gd, sqlite3 steps: - 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 }}-phpextensions + + - name: Cache extensions + uses: actions/cache@v2 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ steps.extcache.outputs.key }} + restore-keys: ${{ env.extensions }} + - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - extensions: gd, sqlite3 + extensions: ${{ env.extensions }} # composer cache - name: Remove composer lock From e2ae0da4e1d310447c2847ea7cdde84e3109bc40 Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 00:41:54 +0200 Subject: [PATCH 165/478] Style cleanup adding newlines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seems to be the unofficial GitHub Actions YAML style and arguably makes things a lot more readable if you have a lot of steps… --- .github/workflows/tests.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 271cfc5..5cf16ee 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: @@ -11,6 +12,7 @@ jobs: run: composer validate - name: Install dependencies run: composer install --prefer-dist --no-dev + PHPunit: runs-on: ubuntu-latest strategy: @@ -19,7 +21,10 @@ jobs: name: PHP ${{ matrix.php-versions }} unit tests on ${{ matrix.operating-system }} env: extensions: gd, sqlite3 + steps: + + # let's get started! - name: Checkout uses: actions/checkout@v2 @@ -48,9 +53,11 @@ jobs: # 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 @@ -58,6 +65,7 @@ jobs: run: | echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")" shell: bash + - name: Cache dependencies uses: actions/cache@v2 with: @@ -75,20 +83,26 @@ jobs: - 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 with: node-version: '12' + - 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 From a372ee92e950a164e15f162af37ceccd09fe81a5 Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 00:43:54 +0200 Subject: [PATCH 166/478] Fix wrong cache key --- .github/workflows/tests.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5cf16ee..a991262 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,6 +21,7 @@ jobs: name: PHP ${{ matrix.php-versions }} unit tests on ${{ matrix.operating-system }} env: extensions: gd, sqlite3 + extensions-cache-key: ${{ runner.os }}-phpextensions steps: @@ -35,14 +36,14 @@ jobs: with: php-version: ${{ matrix.php-versions }} extensions: ${{ env.extensions }} - key: ${{ runner.os }}-phpextensions + key: ${{ env.extensions-cache-key }} - name: Cache extensions uses: actions/cache@v2 with: path: ${{ steps.extcache.outputs.dir }} key: ${{ steps.extcache.outputs.key }} - restore-keys: ${{ env.extensions }} + restore-keys: ${{ env.extensions-cache-key }} - name: Setup PHP uses: shivammathur/setup-php@v2 From b80732f8e20bc1e2fb824b5edcdb87efab16580e Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 00:55:08 +0200 Subject: [PATCH 167/478] Add caching for NodeJS --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a991262..250aedb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -93,9 +93,11 @@ jobs: 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-lock.json' - name: Setup Mocha run: npm install -g mocha From 5f4fe52eabad233631549b72022eedc00bfdce2c Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 00:56:44 +0200 Subject: [PATCH 168/478] Use package-json instead of package-lock.json for cache --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 250aedb..37c8953 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,7 +97,7 @@ jobs: with: node-version: '12' cache: 'npm' - cache-dependency-path: 'js/package-lock.json' + cache-dependency-path: 'js/package.json' - name: Setup Mocha run: npm install -g mocha From ab11fbeb471b7662721970da0f17c72a18fe3eff Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 01:01:24 +0200 Subject: [PATCH 169/478] Fix syntax error Apparently in envs the OS etc. syntax is not supported, so we need to use it like this. --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 37c8953..8c3b1c0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,7 @@ jobs: name: PHP ${{ matrix.php-versions }} unit tests on ${{ matrix.operating-system }} env: extensions: gd, sqlite3 - extensions-cache-key: ${{ runner.os }}-phpextensions + extensions-cache-key-name: phpextensions steps: @@ -36,14 +36,14 @@ jobs: with: php-version: ${{ matrix.php-versions }} extensions: ${{ env.extensions }} - key: ${{ env.extensions-cache-key }} + 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: ${{ env.extensions-cache-key }} + restore-keys: ${{ runner.os }}-${{ env.extensions-cache-key }} - name: Setup PHP uses: shivammathur/setup-php@v2 From f43a41c11792fcdb7fd48937663da37a912c8945 Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 01:07:57 +0200 Subject: [PATCH 170/478] Update tests.yml --- .github/workflows/tests.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8c3b1c0..0ef1946 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,6 +50,14 @@ jobs: with: php-version: ${{ matrix.php-versions }} 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 From f4e68fcc04a8f2567ab230f11645f85664e7e1f1 Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 2 Oct 2021 01:12:08 +0200 Subject: [PATCH 171/478] style: better YAML comments --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8c3b1c0..c10595e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -74,9 +74,10 @@ jobs: key: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}- - # composer + # composer installation - name: Setup PHPunit run: composer install -n + - name: Install Google Cloud Storage run: composer require google/cloud-storage From a988be7431dd176bdfacea5a5bcdde4489ed1ddb Mon Sep 17 00:00:00 2001 From: rugk Date: Wed, 6 Oct 2021 20:13:09 +0200 Subject: [PATCH 172/478] Add CI for automatic PHP8 updates Adds a simple CI for pushing the master branches changes to the php8 branch. Useful/discussed for https://github.com/PrivateBin/PrivateBin/issues/707 --- .github/workflows/refresh-php8.yml | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/refresh-php8.yml diff --git a/.github/workflows/refresh-php8.yml b/.github/workflows/refresh-php8.yml new file mode 100644 index 0000000..bf43df4 --- /dev/null +++ b/.github/workflows/refresh-php8.yml @@ -0,0 +1,40 @@ +# This is a basic workflow to help you get started with Actions + +name: Refresh PHP 8 branch + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [ master ] + + schedule: + - cron: '42 2 * * *' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - 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' + From c7cd450f9b71eb7df1b4d70dd84be54b5bd83f2d Mon Sep 17 00:00:00 2001 From: rugk Date: Wed, 6 Oct 2021 20:19:03 +0200 Subject: [PATCH 173/478] Remove useless boilerplate comments --- .github/workflows/refresh-php8.yml | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/workflows/refresh-php8.yml b/.github/workflows/refresh-php8.yml index bf43df4..5160e22 100644 --- a/.github/workflows/refresh-php8.yml +++ b/.github/workflows/refresh-php8.yml @@ -1,29 +1,17 @@ -# This is a basic workflow to help you get started with Actions - name: Refresh PHP 8 branch -# Controls when the workflow will run on: - # Triggers the workflow on push or pull request events but only for the master branch push: - branches: [ master ] - + branches: [ master ] schedule: - cron: '42 2 * * *' - - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: -# A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - # This workflow contains a single job called "build" build: - # The type of runner that the job will run on runs-on: ubuntu-latest - # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - name: Checkout php8 branch From 307443dac3d11c25d7d2d5cffe50c4961b7d78c7 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Fri, 8 Oct 2021 18:28:00 +0200 Subject: [PATCH 174/478] New translations en.json (Czech) --- i18n/cs.json | 78 ++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/i18n/cs.json b/i18n/cs.json index 34ef19a..54ac016 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -6,7 +6,7 @@ "en": "cs", "Paste does not exist, has expired or has been deleted.": "Vložený text neexistuje, expiroval nebo byl odstraněn.", "%s requires php %s or above to work. Sorry.": "%s vyžaduje php %s nebo vyšší. Lituji.", - "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", + "%s requires configuration section [%s] to be present in configuration file.": "%s vyžaduje, aby byla v konfiguračním souboru přítomna sekce [%s].", "Please wait %d seconds between each post.": [ "Počet sekund do dalšího příspěvku: %d.", "Počet sekund do dalšího příspěvku: %d.", @@ -19,10 +19,10 @@ "Error saving comment. Sorry.": "Chyba při ukládání komentáře.", "Error saving paste. Sorry.": "Chyba při ukládání příspěvku.", "Invalid paste ID.": "Chybně vložené ID.", - "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", - "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", - "Paste was properly deleted.": "Paste was properly deleted.", - "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", + "Paste is not of burn-after-reading type.": "Příspěvek není nastaven na smazaní po přečtení.", + "Wrong deletion token. Paste was not deleted.": "Chybný token pro odstranění. Příspěvek nebyl smazán.", + "Paste was properly deleted.": "Příspěvek byl řádně smazán.", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "Pro fungování %s je vyžadován JavaScript. Omlouváme se za nepříjemnosti.", "%s requires a modern browser to work.": "%%s requires a modern browser to work.", "New": "Nový", "Send": "Odeslat", @@ -33,7 +33,7 @@ "Open discussion": "Povolit komentáře", "Password (recommended)": "Heslo (doporučeno)", "Discussion": "Komentáře", - "Toggle navigation": "Toggle navigation", + "Toggle navigation": "Přepnout navigaci", "%d seconds": [ "%d sekuda", "%d sekundy", @@ -77,7 +77,7 @@ "%d years (3rd plural)" ], "Never": "Nikdy", - "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.", + "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Poznámka: Toto služba slouží ke vyzkoušení: Data mohou být kdykoliv smazána. Při zneužití této služby zemřou koťátka.", "This document will expire in %d seconds.": [ "Tento dokument expiruje za %d sekundu.", "Tento dokument expiruje za %d sekundy.", @@ -109,19 +109,19 @@ "Tento dokument expiruje za %d měsíců." ], "Please enter the password for this paste:": "Zadejte prosím heslo:", - "Could not decrypt data (Wrong key?)": "Could not decrypt data (Wrong key?)", - "Could not delete the paste, it was not stored in burn after reading mode.": "Could not delete the paste, it was not stored in burn after reading mode.", - "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.", - "Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?", - "Reply": "Reply", + "Could not decrypt data (Wrong key?)": "Nepodařilo se dešifrovat data (Špatný klíč?)", + "Could not delete the paste, it was not stored in burn after reading mode.": "Nepodařilo se odstranit příspěvek, nebyl uložen v režimu smazání po přečtení.", + "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "POUZE PRO VAŠE OČI. Nezavírejte toto okno, tuto zprávu nelze znovu zobrazit.", + "Could not decrypt comment; Wrong key?": "Nepodařilo se dešifrovat komentář; Špatný klíč?", + "Reply": "Odpovědět", "Anonymous": "Anonym", - "Avatar generated from IP address": "Avatar generated from IP address", + "Avatar generated from IP address": "Avatar vygenerován z IP adresy", "Add comment": "Přidat komentář", "Optional nickname…": "Volitelný nickname…", "Post comment": "Odeslat komentář", "Sending comment…": "Odesílání komentáře…", "Comment posted.": "Komentář odeslán.", - "Could not refresh display: %s": "Could not refresh display: %s", + "Could not refresh display: %s": "Nepodařilo se obnovit zobrazení: %s", "unknown status": "neznámý stav", "server error or not responding": "Chyba na serveru nebo server neodpovídá", "Could not post comment: %s": "Nelze odeslat komentář: %s", @@ -145,45 +145,45 @@ "Markdown": "Markdown", "Download attachment": "Stáhnout přílohu", "Cloned: '%s'": "Klonováno: '%s'", - "The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.", + "The cloned file '%s' was attached to this paste.": "Naklonovaný soubor '%s' byl připojen k tomuto příspěvku.", "Attach a file": "Připojit soubor", - "alternatively drag & drop a file or paste an image from the clipboard": "alternatively drag & drop a file or paste an image from the clipboard", + "alternatively drag & drop a file or paste an image from the clipboard": "alternativně přetáhněte soubor nebo vložte obrázek ze schránky", "File too large, to display a preview. Please download the attachment.": "Soubor je příliš velký pro zobrazení náhledu. Stáhněte si přílohu.", "Remove attachment": "Odstranit přílohu", "Your browser does not support uploading encrypted files. Please use a newer browser.": "Váš prohlížeč nepodporuje nahrávání šifrovaných souborů. Použijte modernější verzi prohlížeče.", "Invalid attachment.": "Chybná příloha.", "Options": "Volby", - "Shorten URL": "Shorten URL", + "Shorten URL": "Zkrátit URL", "Editor": "Editor", "Preview": "Náhled", - "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.", - "Decrypt": "Decrypt", + "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s vyžaduje, aby PATH skončil s \"%s\". Aktualizujte PATH ve vašem souboru index.php.", + "Decrypt": "Dešifrovat", "Enter password": "Zadejte heslo", - "Loading…": "Loading…", - "Decrypting paste…": "Decrypting paste…", - "Preparing new paste…": "Preparing new paste…", - "In case this message never disappears please have a look at this FAQ for information to troubleshoot.": "In case this message never disappears please have a look at this FAQ for information to troubleshoot.", + "Loading…": "Načítání…", + "Decrypting paste…": "Dešifruji příspěvek…", + "Preparing new paste…": "Připravuji nový příspěvek…", + "In case this message never disappears please have a look at this FAQ for information to troubleshoot.": "V případě, že tato zpráva nezmizí, se podívejte na tyto často kladené otázky pro řešení.", "+++ no paste text +++": "+++ žádný vložený text +++", - "Could not get paste data: %s": "Could not get paste data: %s", - "QR code": "QR code", - "This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.", - "For more information 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:", + "Could not get paste data: %s": "Nepodařilo se získat data příspěvku: %s", + "QR code": "QR kód", + "This website is using an insecure HTTP connection! Please use it only for testing.": "Tato stránka používá nezabezpečený připojení HTTP! Použijte ji prosím jen pro testování.", + "For more information see this FAQ entry.": "Více informací naleznete v této položce FAQ.", + "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Váš prohlížeč může vyžadovat připojení HTTPS pro podporu WebCrypto API. Zkuste přepnout na HTTPS.", + "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Váš prohlížeč nepodporuje WebAssembly, který se používá pro zlib kompresi. Můžete vytvořit nekomprimované dokumenty, ale nebudete moct číst ty komprimované.", + "waiting on user to provide a password": "čekám na zadání hesla", + "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Nepodařilo se dešifrovat data. Zadali jste špatné heslo? Zkuste to znovu pomocí tlačítka nahoře.", + "Retry": "Opakovat", + "Showing raw text…": "Zobrazuji surový text…", + "Notice:": "Upozornění:", + "This link will expire after %s.": "Tento odkaz vyprší za %s.", + "This link can only be accessed once, do not use back or refresh button in your browser.": "Tento odkaz je přístupný pouze jednou, nepoužívejte tlačítko zpět ani neobnovujte tuto stránku ve vašem prohlížeči.", + "Link:": "Odkaz:", "Recipient may become aware of your timezone, convert time to UTC?": "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" + "URL shortener may expose your decrypt key in URL.": "Zkracovač URL může odhalit váš dešifrovací klíč v URL.", + "Save paste": "Uložit příspěvek" } From 808560438505aae0ae1d7de95f97c3df342fcfcc Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Fri, 8 Oct 2021 19:40:15 +0200 Subject: [PATCH 175/478] New translations en.json (Czech) --- i18n/cs.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/i18n/cs.json b/i18n/cs.json index 54ac016..e6eff88 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -77,7 +77,7 @@ "%d years (3rd plural)" ], "Never": "Nikdy", - "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Poznámka: Toto služba slouží ke vyzkoušení: Data mohou být kdykoliv smazána. Při zneužití této služby zemřou koťátka.", + "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Poznámka: Tato služba slouží k vyzkoušení: Data mohou být kdykoliv smazána. Při zneužití této služby zemřou koťátka.", "This document will expire in %d seconds.": [ "Tento dokument expiruje za %d sekundu.", "Tento dokument expiruje za %d sekundy.", @@ -178,12 +178,12 @@ "This link will expire after %s.": "Tento odkaz vyprší za %s.", "This link can only be accessed once, do not use back or refresh button in your browser.": "Tento odkaz je přístupný pouze jednou, nepoužívejte tlačítko zpět ani neobnovujte tuto stránku ve vašem prohlížeči.", "Link:": "Odkaz:", - "Recipient may become aware of your timezone, convert time to UTC?": "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.", + "Recipient may become aware of your timezone, convert time to UTC?": "Příjemce se může dozvědět o vašem časovém pásmu, převést čas na UTC?", + "Use Current Timezone": "Použít aktuální časové pásmo", + "Convert To UTC": "Převést na UTC", + "Close": "Zavřít", + "Encrypted note on PrivateBin": "Šifrovaná poznámka ve službě PrivateBin", + "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Navštivte tento odkaz pro zobrazení poznámky. Přeposláním URL umožníte také jiným lidem přístup.", "URL shortener may expose your decrypt key in URL.": "Zkracovač URL může odhalit váš dešifrovací klíč v URL.", "Save paste": "Uložit příspěvek" } From f6421c9c7cf77b63b84bf42cbb132666a5d1270c Mon Sep 17 00:00:00 2001 From: rugk Date: Mon, 11 Oct 2021 17:45:42 +0200 Subject: [PATCH 176/478] Fix PHP8 pipeline As per https://github.com/PrivateBin/PrivateBin/pull/843#issuecomment-939526915 Co-Authored-By: El RIDO --- .github/workflows/refresh-php8.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/refresh-php8.yml b/.github/workflows/refresh-php8.yml index 5160e22..76e8f85 100644 --- a/.github/workflows/refresh-php8.yml +++ b/.github/workflows/refresh-php8.yml @@ -12,10 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Checkout php8 branch - run: git checkout php8 + uses: actions/checkout@v2 + with: + # directly checkout the php8 branch + ref: php8 + # Number of commits to fetch. 0 indicates all history for all branches and tags. + # Default: 1 + fetch-depth: 0 - name: Merge master changes into php8 run: git merge master From af852927a94faeb67934fdb1232837698eaf54c0 Mon Sep 17 00:00:00 2001 From: rugk Date: Wed, 13 Oct 2021 20:07:45 +0200 Subject: [PATCH 177/478] Fix PHP refresh pipeline merge See https://github.com/PrivateBin/PrivateBin/pull/847#issuecomment-942580850 Now merging the origin as master is not yet pulled. --- .github/workflows/refresh-php8.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-php8.yml b/.github/workflows/refresh-php8.yml index 76e8f85..a73c2af 100644 --- a/.github/workflows/refresh-php8.yml +++ b/.github/workflows/refresh-php8.yml @@ -22,7 +22,7 @@ jobs: fetch-depth: 0 - name: Merge master changes into php8 - run: git merge master + run: git merge origin/master - name: Push new changes uses: github-actions-x/commit@v2.8 From 86c5dc9db917ac10cac721ac8c6733221cdfce5d Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 14 Oct 2021 09:04:29 +0200 Subject: [PATCH 178/478] Update de.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Link is not translated as "Verknüpfung", that is a separate term. --- i18n/de.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i18n/de.json b/i18n/de.json index 7bc38d1..0c4cd70 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -175,15 +175,15 @@ "Retry": "Wiederholen", "Showing raw text…": "Rohtext wird angezeigt…", "Notice:": "Hinweis:", - "This link will expire after %s.": "Diese Verknüpfung wird in %s ablaufen.", - "This link can only be accessed once, do not use back or refresh button in your browser.": "Diese Verknüpfung kann nur einmal geöffnet werden, verwende nicht den Zurück- oder Neu-laden-Knopf Deines Browsers.", - "Link:": "Verknüpfung:", + "This link will expire after %s.": "Dieser Link wird am %s ablaufen.", + "This link can only be accessed once, do not use back or refresh button in your browser.": "Dieser Link kann nur einmal geöffnet werden, verwende nicht den Zurück- oder Neu-laden-Knopf Deines Browsers.", + "Link:": "Link:", "Recipient may become aware of your timezone, convert time to UTC?": "Der Empfänger könnte Deine Zeitzone erfahren, möchtest Du die Zeit in UTC umwandeln?", "Use Current Timezone": "Aktuelle Zeitzone verwenden", "Convert To UTC": "In UTC umwandeln", "Close": "Schliessen", "Encrypted note on PrivateBin": "Verschlüsselte Notiz auf PrivateBin", - "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besuche diese Verknüpfung um das Dokument zu sehen. Wird die URL an eine andere Person gegeben, so kann diese Person ebenfalls auf dieses Dokument zugreifen.", + "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besuche diesen Link um das Dokument zu sehen. Wird die URL an eine andere Person gegeben, so kann diese Person ebenfalls auf dieses Dokument zugreifen.", "URL shortener may expose your decrypt key in URL.": "Der URL-Verkürzer kann den Schlüssel in der URL enthüllen.", "Save paste": "Text speichern" } From aa6e2f7631106b691f8154b5f64a09a899e26c38 Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 23 Oct 2021 15:04:54 +0200 Subject: [PATCH 179/478] Set GitHub Bot as author for PHP8 merge commits --- .github/workflows/refresh-php8.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/refresh-php8.yml b/.github/workflows/refresh-php8.yml index a73c2af..45b6b84 100644 --- a/.github/workflows/refresh-php8.yml +++ b/.github/workflows/refresh-php8.yml @@ -27,6 +27,8 @@ jobs: - name: Push new changes uses: github-actions-x/commit@v2.8 with: + name: github-actions[bot] + email: 41898282+github-actions[bot]@users.noreply.github.com github-token: ${{ secrets.GITHUB_TOKEN }} push-branch: 'php8' From 1fff4bf4d7442dccc7cf8ff24dd693354d83ab0a Mon Sep 17 00:00:00 2001 From: rugk Date: Sat, 30 Oct 2021 16:53:42 +0200 Subject: [PATCH 180/478] Also set author for merge commit Follow-up of 41898282+github-actions[bot]@users.noreply.github.com again In contrast to your suggestion, @elrido, I did use GitHubs bot account again. The mails won't spam anyone, and it's actually intended for such stuff. Also, we get a proper avatar on GitHub's commit messages etc., and of course we know it is actually GitHubs (servers) that do this change. --- .github/workflows/refresh-php8.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/refresh-php8.yml b/.github/workflows/refresh-php8.yml index 45b6b84..04e4297 100644 --- a/.github/workflows/refresh-php8.yml +++ b/.github/workflows/refresh-php8.yml @@ -11,7 +11,7 @@ jobs: build: runs-on: ubuntu-latest - steps: + steps: - name: Checkout php8 branch uses: actions/checkout@v2 with: @@ -22,7 +22,10 @@ jobs: fetch-depth: 0 - name: Merge master changes into php8 - run: git merge origin/master + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git merge origin/master - name: Push new changes uses: github-actions-x/commit@v2.8 From b80b318e38f1b6366e0e1c073305e48b927e8625 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 30 Oct 2021 17:23:09 +0200 Subject: [PATCH 181/478] spaces --- .github/workflows/refresh-php8.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/refresh-php8.yml b/.github/workflows/refresh-php8.yml index 04e4297..4c131b4 100644 --- a/.github/workflows/refresh-php8.yml +++ b/.github/workflows/refresh-php8.yml @@ -11,7 +11,7 @@ jobs: build: runs-on: ubuntu-latest - steps: + steps: - name: Checkout php8 branch uses: actions/checkout@v2 with: @@ -26,7 +26,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git merge origin/master - + - name: Push new changes uses: github-actions-x/commit@v2.8 with: From 02fcc0ba53fbabb3a21dc64189ee5e8145d83722 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 6 Jan 2022 13:18:29 +0100 Subject: [PATCH 182/478] New translations en.json (Catalan) --- i18n/ca.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/i18n/ca.json b/i18n/ca.json index ef6b348..a181f3d 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -20,8 +20,8 @@ "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.", + "Wrong deletion token. Paste was not deleted.": "El token d'eliminació és incorrecte. El Paste no s'ha eliminat.", + "Paste was properly deleted.": "El Paste s'ha esborrat correctament.", "JavaScript is required for %s to work. Sorry for the inconvenience.": "Cal JavaScript perquè %s funcioni. Em sap greu les molèsties.", "%s requires a modern browser to work.": "%s requereix un navegador modern per funcionar.", "New": "Nou", @@ -35,16 +35,16 @@ "Discussion": "Discussió", "Toggle navigation": "Alternar navegació", "%d seconds": [ - "%d second (singular)", - "%d seconds (1st plural)", - "%d seconds (2nd plural)", - "%d seconds (3rd plural)" + "%d segon (singular)", + "%d segons (1r plural)", + "%d segons (2n plural)", + "%d segons (3r plural)" ], "%d minutes": [ - "%d minute (singular)", - "%d minutes (1st plural)", - "%d minutes (2nd plural)", - "%d minutes (3rd plural)" + "%d minut (singular)", + "%d minuts (1r plural)", + "%d minuts (2n plural)", + "%d minuts (3r plural)" ], "%d hours": [ "%d hour (singular)", From 74e1f18ae08c45a48db70e7e863161e3cb331684 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 6 Jan 2022 17:10:34 +0100 Subject: [PATCH 183/478] New translations en.json (Catalan) --- i18n/ca.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/i18n/ca.json b/i18n/ca.json index a181f3d..616cb20 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -8,10 +8,10 @@ "%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)" + "Espereu %d segon entre cada entrada.", + "Espereu %d segons entre cada entrada.", + "Please wait %d seconds between each post. (2nd plural)", + "Please wait %d seconds between each post. (3rd plural)" ], "Paste is limited to %s of encrypted data.": "L'enganxat està limitat a %s de dades encriptades.", "Invalid data.": "Dades no vàlides.", @@ -35,20 +35,20 @@ "Discussion": "Discussió", "Toggle navigation": "Alternar navegació", "%d seconds": [ - "%d segon (singular)", - "%d segons (1r plural)", - "%d segons (2n plural)", - "%d segons (3r plural)" + "%d segon", + "%d segons", + "%d seconds (2nd plural)", + "%d seconds (3rd plural)" ], "%d minutes": [ - "%d minut (singular)", - "%d minuts (1r plural)", - "%d minuts (2n plural)", - "%d minuts (3r plural)" + "%d minut", + "%d minuts", + "%d minutes (2nd plural)", + "%d minutes (3rd plural)" ], "%d hours": [ "%d hour (singular)", - "%d hours (1st plural)", + "%d hores", "%d hours (2nd plural)", "%d hours (3rd plural)" ], From e53090a93761a88137b9e62e63a285a88cb0f558 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 6 Jan 2022 18:06:35 +0100 Subject: [PATCH 184/478] New translations en.json (Catalan) --- i18n/ca.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/i18n/ca.json b/i18n/ca.json index 616cb20..50db9fa 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -47,32 +47,32 @@ "%d minutes (3rd plural)" ], "%d hours": [ - "%d hour (singular)", + "%d hora", "%d hores", "%d hours (2nd plural)", "%d hours (3rd plural)" ], "%d days": [ - "%d day (singular)", - "%d days (1st plural)", + "%d dia", + "%d dies", "%d days (2nd plural)", "%d days (3rd plural)" ], "%d weeks": [ - "%d week (singular)", - "%d weeks (1st plural)", + "%d setmana", + "%d setmanes", "%d weeks (2nd plural)", "%d weeks (3rd plural)" ], "%d months": [ - "%d month (singular)", - "%d months (1st plural)", + "%d mes", + "%d mesos", "%d months (2nd plural)", "%d months (3rd plural)" ], "%d years": [ - "%d year (singular)", - "%d years (1st plural)", + "%d any", + "%d anys", "%d years (2nd plural)", "%d years (3rd plural)" ], From ff19d052b727f346b19479c5aa1ed943a9470ec8 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 10 Jan 2022 21:11:59 +0100 Subject: [PATCH 185/478] disable StyleCI rule causing 'Unterminated comment' false positive --- .styleci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.styleci.yml b/.styleci.yml index 9c2c76c..5281f15 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -18,6 +18,7 @@ disabled: - declare_equal_normalize - heredoc_to_nowdoc - method_argument_space_strict + - multiline_comment_opening_closing - new_with_braces - no_alternative_syntax - phpdoc_align From eef4f8be3fb843bd029cfa53051588cf5ccb61b0 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 10 Jan 2022 21:21:12 +0100 Subject: [PATCH 186/478] exclude configuration files from StyleCI scan, causing 'Unterminated comment' false positive --- .styleci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index 5281f15..3fa16b8 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -18,7 +18,6 @@ disabled: - declare_equal_normalize - heredoc_to_nowdoc - method_argument_space_strict - - multiline_comment_opening_closing - new_with_braces - no_alternative_syntax - phpdoc_align @@ -30,3 +29,7 @@ disabled: - short_array_syntax - single_line_after_imports - unalign_equals + +finder: + exclude: + - "cfg" From ca999b15edaca33140650ce95d5e0fb4867f1f24 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 10 Jan 2022 21:26:28 +0100 Subject: [PATCH 187/478] limit files of StyleCI scan --- .styleci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.styleci.yml b/.styleci.yml index 3fa16b8..7975d74 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -31,5 +31,7 @@ disabled: - unalign_equals finder: - exclude: - - "cfg" + path: + - "lib/" + - "tpl/" + - "tst/" From ee99952d90578065c427e823b8c44dbb61f0d90c Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Mon, 17 Jan 2022 20:06:26 -0500 Subject: [PATCH 188/478] Support OCI (Read/Write) --- lib/Data/Database.php | 127 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 23 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 0c66d33..a2b1fe1 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -199,12 +199,30 @@ class Database extends AbstractData $burnafterreading = $paste['adata'][3]; } try { + $big_string = $isVersion1 ? $paste['data'] : Json::encode($paste); + if (self::$_type === 'oci') { + # It is not possible to execute in the normal way if strlen($big_string) >= 4000 + $stmt = self::$_db->prepare( + 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . + ' VALUES(?,?,?,?,?,?,?,?,?)' + ); + $stmt->bindParam(1, $pasteid); + $stmt->bindParam(2, $big_string, PDO::PARAM_STR, strlen($big_string)); + $stmt->bindParam(3, $created, PDO::PARAM_INT); + $stmt->bindParam(4, $expire_date, PDO::PARAM_INT); + $stmt->bindParam(5, $opendiscussion, PDO::PARAM_INT); + $stmt->bindParam(6, $burnafterreading, PDO::PARAM_INT); + $stmt->bindParam(7, Json::encode($meta)); + $stmt->bindParam(8, $attachment, PDO::PARAM_STR, strlen($attachment)); + $stmt->bindParam(9, $attachmentname); + return $stmt->execute(); + } return self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . ' VALUES(?,?,?,?,?,?,?,?,?)', array( $pasteid, - $isVersion1 ? $paste['data'] : Json::encode($paste), + $big_string, $created, $expire_date, (int) $opendiscussion, @@ -233,11 +251,15 @@ class Database extends AbstractData } self::$_cache[$pasteid] = false; + $rawData = ""; try { $paste = self::_select( 'SELECT * FROM ' . self::_sanitizeIdentifier('paste') . ' WHERE dataid = ?', array($pasteid), true ); + if ($paste !== false) { + $rawData = self::$_type === 'oci' ? self::_clob($paste['DATA']) : $paste['data']; + } } catch (Exception $e) { $paste = false; } @@ -245,25 +267,25 @@ class Database extends AbstractData return false; } // create array - $data = Json::decode($paste['data']); + $data = Json::decode($rawData); $isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2; if ($isVersion2) { self::$_cache[$pasteid] = $data; list($createdKey) = self::_getVersionedKeys(2); } else { - self::$_cache[$pasteid] = array('data' => $paste['data']); + self::$_cache[$pasteid] = array('data' => $paste[self::_sanitizeColumn('data')]); list($createdKey) = self::_getVersionedKeys(1); } try { - $paste['meta'] = Json::decode($paste['meta']); + $paste['meta'] = Json::decode($paste[self::_sanitizeColumn('meta')]); } catch (Exception $e) { $paste['meta'] = array(); } $paste = self::upgradePreV1Format($paste); self::$_cache[$pasteid]['meta'] = $paste['meta']; - self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste['postdate']; - $expire_date = (int) $paste['expiredate']; + self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste[self::_sanitizeColumn('postdate')]; + $expire_date = (int) $paste[self::_sanitizeColumn('expiredate')]; if ($expire_date > 0) { self::$_cache[$pasteid]['meta']['expire_date'] = $expire_date; } @@ -272,16 +294,16 @@ class Database extends AbstractData } // support v1 attachments - if (array_key_exists('attachment', $paste) && strlen($paste['attachment'])) { - self::$_cache[$pasteid]['attachment'] = $paste['attachment']; - if (array_key_exists('attachmentname', $paste) && strlen($paste['attachmentname'])) { - self::$_cache[$pasteid]['attachmentname'] = $paste['attachmentname']; + if (array_key_exists(self::_sanitizeColumn('attachment'), $paste) && strlen($paste[self::_sanitizeColumn('attachment')])) { + self::$_cache[$pasteid]['attachment'] = $paste[self::_sanitizeColumn('attachment')]; + if (array_key_exists(self::_sanitizeColumn('attachmentname'), $paste) && strlen($paste[self::_sanitizeColumn('attachmentname')])) { + self::$_cache[$pasteid]['attachmentname'] = $paste[self::_sanitizeColumn('attachmentname')]; } } - if ($paste['opendiscussion']) { + if ($paste[self::_sanitizeColumn('opendiscussion')]) { self::$_cache[$pasteid]['meta']['opendiscussion'] = true; } - if ($paste['burnafterreading']) { + if ($paste[self::_sanitizeColumn('burnafterreading')]) { self::$_cache[$pasteid]['meta']['burnafterreading'] = true; } @@ -356,6 +378,21 @@ class Database extends AbstractData } } try { + if (self::$_type === 'oci') { + # It is not possible to execute in the normal way if strlen($big_string) >= 4000 + $stmt = self::$_db->prepare( + 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . + ' VALUES(?,?,?,?,?,?,?)' + ); + $stmt->bindParam(1, $commentid); + $stmt->bindParam(2, $pasteid); + $stmt->bindParam(3, $parentid); + $stmt->bindParam(4, $data, PDO::PARAM_STR, strlen($data)); + $stmt->bindParam(5, $meta['nickname']); + $stmt->bindParam(6, $meta[$iconKey]); + $stmt->bindParam(7, $meta[$createdKey], PDO::PARAM_INT); + return $stmt->execute(); + } return self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . ' VALUES(?,?,?,?,?,?,?)', @@ -392,22 +429,33 @@ class Database extends AbstractData $comments = array(); if (count($rows)) { foreach ($rows as $row) { - $i = $this->getOpenSlot($comments, (int) $row['postdate']); - $data = Json::decode($row['data']); + $i = $this->getOpenSlot($comments, (int) $row[self::_sanitizeColumn('postdate')]); + $id = $row[self::_sanitizeColumn('dataid')]; + if (self::$_type === 'oci') { + $newrow = self::_select( + 'SELECT data FROM ' . self::_sanitizeIdentifier('comment') . + ' WHERE dataid = ?', array($id), true + ); + $rawData = self::_clob($newrow['DATA']); + } + else { + $rawData = $row['data']; + } + $data = Json::decode($rawData); if (array_key_exists('v', $data) && $data['v'] >= 2) { $version = 2; $comments[$i] = $data; } else { $version = 1; - $comments[$i] = array('data' => $row['data']); + $comments[$i] = array('data' => $rawData); } list($createdKey, $iconKey) = self::_getVersionedKeys($version); - $comments[$i]['id'] = $row['dataid']; - $comments[$i]['parentid'] = $row['parentid']; - $comments[$i]['meta'] = array($createdKey => (int) $row['postdate']); + $comments[$i]['id'] = $id; + $comments[$i]['parentid'] = $row[self::_sanitizeColumn('parentid')]; + $comments[$i]['meta'] = array($createdKey => (int) $row[self::_sanitizeColumn('postdate')]); foreach (array('nickname' => 'nickname', 'vizhash' => $iconKey) as $rowKey => $commentKey) { - if (array_key_exists($rowKey, $row) && !empty($row[$rowKey])) { - $comments[$i]['meta'][$commentKey] = $row[$rowKey]; + if (array_key_exists(self::_sanitizeColumn($rowKey), $row) && !empty($row[self::_sanitizeColumn($rowKey)])) { + $comments[$i]['meta'][$commentKey] = $row[self::_sanitizeColumn($rowKey)]; } } } @@ -518,7 +566,8 @@ class Database extends AbstractData $pastes = array(); $rows = self::_select( 'SELECT dataid FROM ' . self::_sanitizeIdentifier('paste') . - ' WHERE expiredate < ? AND expiredate != ? LIMIT ?', + ' WHERE expiredate < ? AND expiredate != ? ' . + (self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'), array(time(), 0, $batchsize) ); if (count($rows)) { @@ -658,7 +707,7 @@ class Database extends AbstractData } catch (PDOException $e) { return ''; } - return $row ? $row['value'] : ''; + return $row ? $row[self::_sanitizeColumn('value')] : ''; } /** @@ -789,7 +838,21 @@ class Database extends AbstractData */ private static function _sanitizeIdentifier($identifier) { - return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); + $id = preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); + return self::_sanitizeColumn($id); + } + + /** + * sanitizes column name because OCI + * + * @access private + * @static + * @param string $name + * @return string + */ + private static function _sanitizeColumn($name) + { + return self::$_type === 'oci' ? strtoupper($name) : $name; } /** @@ -865,4 +928,22 @@ class Database extends AbstractData ); } } + + /** + * read CLOB for OCI + * https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field + * + * @access private + * @static + * @param object $column + * @return string + */ + private static function _clob($column) + { + if ($column == null) return null; + $str = ""; + while ($column !== null and $tmp = fread($column, 1024)) + $str .= $tmp; + return $str; + } } From 6a489d35ab39041b3dd793d91beaf8681489bf4f Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 18 Jan 2022 11:21:25 -0500 Subject: [PATCH 189/478] Support OCI (Create table) --- lib/Data/Database.php | 71 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index a2b1fe1..617676c 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -572,7 +572,7 @@ class Database extends AbstractData ); if (count($rows)) { foreach ($rows as $row) { - $pastes[] = $row['dataid']; + $pastes[] = $row[self::_sanitizeIdentifier('dataid')]; } } return $pastes; @@ -710,6 +710,18 @@ class Database extends AbstractData return $row ? $row[self::_sanitizeColumn('value')] : ''; } + /** + * OCI cannot accept semicolons + * + * @access private + * @static + * @return string + */ + private static function _getSemicolon() + { + return self::$_type === 'oci' ? "" : ";"; + } + /** * get the primary key clauses, depending on the database driver * @@ -721,7 +733,7 @@ class Database extends AbstractData private static function _getPrimaryKeyClauses($key = 'dataid') { $main_key = $after_key = ''; - if (self::$_type === 'mysql') { + if (self::$_type === 'mysql' || self::$_type === 'oci') { $after_key = ", PRIMARY KEY ($key)"; } else { $main_key = ' PRIMARY KEY'; @@ -740,13 +752,13 @@ class Database extends AbstractData */ private static function _getDataType() { - return self::$_type === 'pgsql' ? 'TEXT' : 'BLOB'; + return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'BLOB'); } /** * get the attachment type, depending on the database driver * - * PostgreSQL uses a different API for BLOBs then SQL, hence we use TEXT + * PostgreSQL and OCI use different APIs for BLOBs then SQL, hence we use TEXT and CLOB * * @access private * @static @@ -754,7 +766,21 @@ class Database extends AbstractData */ private static function _getAttachmentType() { - return self::$_type === 'pgsql' ? 'TEXT' : 'MEDIUMBLOB'; + return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'CLOB' : 'MEDIUMBLOB'); + } + + /** + * get the meta type, depending on the database driver + * + * OCI can't even accept TEXT so it has to be VARCHAR2(200) + * + * @access private + * @static + * @return string + */ + private static function _getMetaType() + { + return self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'TEXT'; } /** @@ -768,6 +794,7 @@ class Database extends AbstractData list($main_key, $after_key) = self::_getPrimaryKeyClauses(); $dataType = self::_getDataType(); $attachmentType = self::_getAttachmentType(); + $metaType = self::_getMetaType(); self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('paste') . ' ( ' . "dataid CHAR(16) NOT NULL$main_key, " . @@ -776,12 +803,26 @@ class Database extends AbstractData 'expiredate INT, ' . 'opendiscussion INT, ' . 'burnafterreading INT, ' . - 'meta TEXT, ' . + "meta $metaType, " . "attachment $attachmentType, " . - "attachmentname $dataType$after_key );" + "attachmentname $dataType$after_key )" . self::_getSemicolon() ); } + /** + * get the nullable text type, depending on the database driver + * + * OCI will pad CHAR columns with spaces, hence VARCHAR2 + * + * @access private + * @static + * @return string + */ + private static function _getParentType() + { + return self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; + } + /** * create the paste table * @@ -792,19 +833,21 @@ class Database extends AbstractData { list($main_key, $after_key) = self::_getPrimaryKeyClauses(); $dataType = self::_getDataType(); + $parentType = self::_getParentType(); + $attachmentType = self::_getAttachmentType(); self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('comment') . ' ( ' . "dataid CHAR(16) NOT NULL$main_key, " . 'pasteid CHAR(16), ' . - 'parentid CHAR(16), ' . - "data $dataType, " . + "parentid $parentType, " . + "data $attachmentType, " . "nickname $dataType, " . "vizhash $dataType, " . - "postdate INT$after_key );" + "postdate INT$after_key )" . self::_getSemicolon() ); self::$_db->exec( - 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid);' + 'CREATE INDEX comment_parent ON ' . + self::_sanitizeIdentifier('comment') . '(pasteid)' . self::_getSemicolon() ); } @@ -817,9 +860,11 @@ class Database extends AbstractData private static function _createConfigTable() { list($main_key, $after_key) = self::_getPrimaryKeyClauses('id'); + $charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; + $textType = self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'TEXT'; self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('config') . - " ( id CHAR(16) NOT NULL$main_key, value TEXT$after_key );" + " ( id $charType NOT NULL$main_key, value $textType$after_key )" . self::_getSemicolon() ); self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('config') . From 041ef7f7a5865c6bf8163e9f8219a2029d05a2e1 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Thu, 20 Jan 2022 13:33:23 -0500 Subject: [PATCH 190/478] Support OCI (Satisfy the CI) --- lib/Data/Database.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 617676c..a89da4b 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -199,9 +199,10 @@ class Database extends AbstractData $burnafterreading = $paste['adata'][3]; } try { - $big_string = $isVersion1 ? $paste['data'] : Json::encode($paste); + $big_string = $isVersion1 ? $paste['data'] : Json::encode($paste); + $metajson = Json::encode($meta); if (self::$_type === 'oci') { - # It is not possible to execute in the normal way if strlen($big_string) >= 4000 + // It is not possible to execute in the normal way if strlen($big_string) >= 4000 $stmt = self::$_db->prepare( 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . ' VALUES(?,?,?,?,?,?,?,?,?)' @@ -212,7 +213,7 @@ class Database extends AbstractData $stmt->bindParam(4, $expire_date, PDO::PARAM_INT); $stmt->bindParam(5, $opendiscussion, PDO::PARAM_INT); $stmt->bindParam(6, $burnafterreading, PDO::PARAM_INT); - $stmt->bindParam(7, Json::encode($meta)); + $stmt->bindParam(7, $metajson); $stmt->bindParam(8, $attachment, PDO::PARAM_STR, strlen($attachment)); $stmt->bindParam(9, $attachmentname); return $stmt->execute(); @@ -251,7 +252,7 @@ class Database extends AbstractData } self::$_cache[$pasteid] = false; - $rawData = ""; + $rawData = ''; try { $paste = self::_select( 'SELECT * FROM ' . self::_sanitizeIdentifier('paste') . @@ -379,7 +380,7 @@ class Database extends AbstractData } try { if (self::$_type === 'oci') { - # It is not possible to execute in the normal way if strlen($big_string) >= 4000 + // It is not possible to execute in the normal way if strlen($big_string) >= 4000 $stmt = self::$_db->prepare( 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . ' VALUES(?,?,?,?,?,?,?)' @@ -437,8 +438,7 @@ class Database extends AbstractData ' WHERE dataid = ?', array($id), true ); $rawData = self::_clob($newrow['DATA']); - } - else { + } else { $rawData = $row['data']; } $data = Json::decode($rawData); @@ -719,7 +719,7 @@ class Database extends AbstractData */ private static function _getSemicolon() { - return self::$_type === 'oci' ? "" : ";"; + return self::$_type === 'oci' ? '' : ';'; } /** @@ -980,15 +980,18 @@ class Database extends AbstractData * * @access private * @static - * @param object $column + * @param resource $column * @return string */ private static function _clob($column) { - if ($column == null) return null; - $str = ""; - while ($column !== null and $tmp = fread($column, 1024)) + if ($column == null) { + return null; + } + $str = ''; + while ($tmp = fread($column, 1024)) { $str .= $tmp; + } return $str; } } From 2182cdd44f092277ce829cb9effd861ceb415c06 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 Jan 2022 08:45:12 +0100 Subject: [PATCH 191/478] generalize OCI handling of queries and results --- CHANGELOG.md | 11 +- CREDITS.md | 1 + lib/Data/Database.php | 242 ++++++++++++++------------------------ tst/Data/DatabaseTest.php | 12 ++ 4 files changed, 105 insertions(+), 161 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d964a57..428a60a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * ADDED: Opt-out of federated learning of cohorts (FLoC) (#776) * ADDED: Configuration option to exempt IPs from the rate-limiter (#787) * ADDED: Google Cloud Storage backend support (#795) + * ADDED: Oracle database support (#868) * CHANGED: Language selection cookie only transmitted over HTTPS (#472) * CHANGED: Upgrading libraries to: random_compat 2.0.20 * CHANGED: Removed automatic `.ini` configuration file migration (#808) @@ -65,7 +66,7 @@ * CHANGED: Upgrading libraries to: DOMpurify 2.0.1 * FIXED: Enabling browsers without WASM to create pastes and read uncompressed ones (#454) * FIXED: Cloning related issues (#489, #491, #493, #494) - * FIXED: Enable file operation only when editing (#497) + * FIXED: Enable file operation only when editing (#497) * FIXED: Clicking 'New' on a previously submitted paste does not blank address bar (#354) * FIXED: Clear address bar when create new paste from existing paste (#479) * FIXED: Discussion section not hiding when new/clone paste is clicked on (#484) @@ -228,7 +229,7 @@ encryption), i18n (translation, counterpart of i18n.php) and helper (stateless u * FIXED: 2 minor corrections to avoid notices in php log. * FIXED: Sources converted to UTF-8. * **Alpha 0.14 (2012-04-20):** - * ADDED: GD presence is checked. + * ADDED: GD presence is checked. * CHANGED: Traffic limiter data files moved to data/ (→easier rights management) * ADDED: "Burn after reading" implemented. Opening the URL will display the paste and immediately destroy it on server. * **Alpha 0.13 (2012-04-18):** @@ -236,16 +237,16 @@ encryption), i18n (translation, counterpart of i18n.php) and helper (stateless u * FIXED: $error not properly initialized in index.php * **Alpha 0.12 (2012-04-18):** * **DISCUSSIONS !** Now you can enable discussions on your pastes. Of course, posted comments and nickname are also encrypted and the server cannot see them. - * This feature implies a change in storage format. You will have to delete all previous pastes in your ZeroBin. + * This feature implies a change in storage format. You will have to delete all previous pastes in your ZeroBin. * Added [[php:vizhash_gd|Vizhash]] as avatars, so you can match posters IP addresses without revealing them. (Same image = same IP). Of course the IP address cannot be deduced from the Vizhash. * Remaining time before expiration is now displayed. - * Explicit tags were added to CSS and jQuery selectors (eg. div#aaa instead of #aaa) to speed up browser. + * Explicit tags were added to CSS and jQuery selectors (eg. div#aaa instead of #aaa) to speed up browser. * Better cleaning of the URL (to make sure the key is not broken by some stupid redirection service) * **Alpha 0.11 (2012-04-12):** * Automatically ignore parameters (such as &utm_source=...) added //after// the anchor by some stupid Web 2.0 services. * First public release. * **Alpha 0.10 (2012-04-12):** - * IE9 does not seem to correctly support ''pre-wrap'' either. Special handling mode activated for all version of IE<10. (Note: **ALL other browsers** correctly support this feature.) + * IE9 does not seem to correctly support ''pre-wrap'' either. Special handling mode activated for all version of IE<10. (Note: **ALL other browsers** correctly support this feature.) * **Alpha 0.9 (2012-04-11):** * Oh bummer... IE 8 is as shitty as IE6/7: Its does not seem to support ''white-space:pre-wrap'' correctly. I had to activate the special handling mode. I still have to test IE 9. * **Alpha 0.8 (2012-04-11):** diff --git a/CREDITS.md b/CREDITS.md index 612749c..6c2f647 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -29,6 +29,7 @@ Sébastien Sauvage - original idea and main developer * Lucas Savva - configurable config file location, NixOS packaging * rodehoed - option to exempt ips from the rate-limiter * Mark van Holsteijn - Google Cloud Storage backend +* Austin Huang - Oracle database support ## Translations * Hexalyse - French diff --git a/lib/Data/Database.php b/lib/Data/Database.php index a89da4b..f616a96 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -199,31 +199,12 @@ class Database extends AbstractData $burnafterreading = $paste['adata'][3]; } try { - $big_string = $isVersion1 ? $paste['data'] : Json::encode($paste); - $metajson = Json::encode($meta); - if (self::$_type === 'oci') { - // It is not possible to execute in the normal way if strlen($big_string) >= 4000 - $stmt = self::$_db->prepare( - 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . - ' VALUES(?,?,?,?,?,?,?,?,?)' - ); - $stmt->bindParam(1, $pasteid); - $stmt->bindParam(2, $big_string, PDO::PARAM_STR, strlen($big_string)); - $stmt->bindParam(3, $created, PDO::PARAM_INT); - $stmt->bindParam(4, $expire_date, PDO::PARAM_INT); - $stmt->bindParam(5, $opendiscussion, PDO::PARAM_INT); - $stmt->bindParam(6, $burnafterreading, PDO::PARAM_INT); - $stmt->bindParam(7, $metajson); - $stmt->bindParam(8, $attachment, PDO::PARAM_STR, strlen($attachment)); - $stmt->bindParam(9, $attachmentname); - return $stmt->execute(); - } return self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . ' VALUES(?,?,?,?,?,?,?,?,?)', array( $pasteid, - $big_string, + $isVersion1 ? $paste['data'] : Json::encode($paste), $created, $expire_date, (int) $opendiscussion, @@ -252,15 +233,11 @@ class Database extends AbstractData } self::$_cache[$pasteid] = false; - $rawData = ''; try { $paste = self::_select( 'SELECT * FROM ' . self::_sanitizeIdentifier('paste') . ' WHERE dataid = ?', array($pasteid), true ); - if ($paste !== false) { - $rawData = self::$_type === 'oci' ? self::_clob($paste['DATA']) : $paste['data']; - } } catch (Exception $e) { $paste = false; } @@ -268,25 +245,25 @@ class Database extends AbstractData return false; } // create array - $data = Json::decode($rawData); + $data = Json::decode($paste['data']); $isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2; if ($isVersion2) { self::$_cache[$pasteid] = $data; list($createdKey) = self::_getVersionedKeys(2); } else { - self::$_cache[$pasteid] = array('data' => $paste[self::_sanitizeColumn('data')]); + self::$_cache[$pasteid] = array('data' => $paste['data']); list($createdKey) = self::_getVersionedKeys(1); } try { - $paste['meta'] = Json::decode($paste[self::_sanitizeColumn('meta')]); + $paste['meta'] = Json::decode($paste['meta']); } catch (Exception $e) { $paste['meta'] = array(); } $paste = self::upgradePreV1Format($paste); self::$_cache[$pasteid]['meta'] = $paste['meta']; - self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste[self::_sanitizeColumn('postdate')]; - $expire_date = (int) $paste[self::_sanitizeColumn('expiredate')]; + self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste['postdate']; + $expire_date = (int) $paste['expiredate']; if ($expire_date > 0) { self::$_cache[$pasteid]['meta']['expire_date'] = $expire_date; } @@ -295,16 +272,16 @@ class Database extends AbstractData } // support v1 attachments - if (array_key_exists(self::_sanitizeColumn('attachment'), $paste) && strlen($paste[self::_sanitizeColumn('attachment')])) { - self::$_cache[$pasteid]['attachment'] = $paste[self::_sanitizeColumn('attachment')]; - if (array_key_exists(self::_sanitizeColumn('attachmentname'), $paste) && strlen($paste[self::_sanitizeColumn('attachmentname')])) { - self::$_cache[$pasteid]['attachmentname'] = $paste[self::_sanitizeColumn('attachmentname')]; + if (array_key_exists('attachment', $paste) && strlen($paste['attachment'])) { + self::$_cache[$pasteid]['attachment'] = $paste['attachment']; + if (array_key_exists('attachmentname', $paste) && strlen($paste['attachmentname'])) { + self::$_cache[$pasteid]['attachmentname'] = $paste['attachmentname']; } } - if ($paste[self::_sanitizeColumn('opendiscussion')]) { + if ($paste['opendiscussion']) { self::$_cache[$pasteid]['meta']['opendiscussion'] = true; } - if ($paste[self::_sanitizeColumn('burnafterreading')]) { + if ($paste['burnafterreading']) { self::$_cache[$pasteid]['meta']['burnafterreading'] = true; } @@ -379,21 +356,6 @@ class Database extends AbstractData } } try { - if (self::$_type === 'oci') { - // It is not possible to execute in the normal way if strlen($big_string) >= 4000 - $stmt = self::$_db->prepare( - 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . - ' VALUES(?,?,?,?,?,?,?)' - ); - $stmt->bindParam(1, $commentid); - $stmt->bindParam(2, $pasteid); - $stmt->bindParam(3, $parentid); - $stmt->bindParam(4, $data, PDO::PARAM_STR, strlen($data)); - $stmt->bindParam(5, $meta['nickname']); - $stmt->bindParam(6, $meta[$iconKey]); - $stmt->bindParam(7, $meta[$createdKey], PDO::PARAM_INT); - return $stmt->execute(); - } return self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . ' VALUES(?,?,?,?,?,?,?)', @@ -430,32 +392,22 @@ class Database extends AbstractData $comments = array(); if (count($rows)) { foreach ($rows as $row) { - $i = $this->getOpenSlot($comments, (int) $row[self::_sanitizeColumn('postdate')]); - $id = $row[self::_sanitizeColumn('dataid')]; - if (self::$_type === 'oci') { - $newrow = self::_select( - 'SELECT data FROM ' . self::_sanitizeIdentifier('comment') . - ' WHERE dataid = ?', array($id), true - ); - $rawData = self::_clob($newrow['DATA']); - } else { - $rawData = $row['data']; - } - $data = Json::decode($rawData); + $i = $this->getOpenSlot($comments, (int) $row['postdate']); + $data = Json::decode($row['data']); if (array_key_exists('v', $data) && $data['v'] >= 2) { $version = 2; $comments[$i] = $data; } else { $version = 1; - $comments[$i] = array('data' => $rawData); + $comments[$i] = array('data' => $row['data']); } list($createdKey, $iconKey) = self::_getVersionedKeys($version); - $comments[$i]['id'] = $id; - $comments[$i]['parentid'] = $row[self::_sanitizeColumn('parentid')]; - $comments[$i]['meta'] = array($createdKey => (int) $row[self::_sanitizeColumn('postdate')]); + $comments[$i]['id'] = $row['dataid']; + $comments[$i]['parentid'] = $row['parentid']; + $comments[$i]['meta'] = array($createdKey => (int) $row['postdate']); foreach (array('nickname' => 'nickname', 'vizhash' => $iconKey) as $rowKey => $commentKey) { - if (array_key_exists(self::_sanitizeColumn($rowKey), $row) && !empty($row[self::_sanitizeColumn($rowKey)])) { - $comments[$i]['meta'][$commentKey] = $row[self::_sanitizeColumn($rowKey)]; + if (array_key_exists($rowKey, $row) && !empty($row[$rowKey])) { + $comments[$i]['meta'][$commentKey] = $row[$rowKey]; } } } @@ -572,7 +524,7 @@ class Database extends AbstractData ); if (count($rows)) { foreach ($rows as $row) { - $pastes[] = $row[self::_sanitizeIdentifier('dataid')]; + $pastes[] = $row['dataid']; } } return $pastes; @@ -591,7 +543,22 @@ class Database extends AbstractData private static function _exec($sql, array $params) { $statement = self::$_db->prepare($sql); - $result = $statement->execute($params); + if (self::$_type === 'oci') { + // It is not possible to execute in the normal way if strlen($param) >= 4000 + foreach ($params as $key => $parameter) { + $position = $key + 1; + if (is_int($parameter)) { + $statement->bindParam($position, $parameter, PDO::PARAM_INT); + } elseif ($length = strlen($parameter) >= 4000) { + $statement->bindParam($position, $parameter, PDO::PARAM_STR, $length); + } else { + $statement->bindParam($position, $parameter); + } + } + $result = $statement->execute(); + } else { + $result = $statement->execute($params); + } $statement->closeCursor(); return $result; } @@ -615,6 +582,14 @@ class Database extends AbstractData $statement->fetch(PDO::FETCH_ASSOC) : $statement->fetchAll(PDO::FETCH_ASSOC); $statement->closeCursor(); + if (self::$_type === 'oci') { + // returned column names are all upper case, convert these back + // returned CLOB values are streams, convert these into strings + $result = array_combine( + array_map('strtolower', array_keys($result)), + array_map('self::_sanitizeClob', array_values($result)) + ); + } return $result; } @@ -707,19 +682,7 @@ class Database extends AbstractData } catch (PDOException $e) { return ''; } - return $row ? $row[self::_sanitizeColumn('value')] : ''; - } - - /** - * OCI cannot accept semicolons - * - * @access private - * @static - * @return string - */ - private static function _getSemicolon() - { - return self::$_type === 'oci' ? '' : ';'; + return $row ? $row['value'] : ''; } /** @@ -744,7 +707,7 @@ class Database extends AbstractData /** * get the data type, depending on the database driver * - * PostgreSQL uses a different API for BLOBs then SQL, hence we use TEXT + * PostgreSQL and OCI uses a different API for BLOBs then SQL, hence we use TEXT and CLOB * * @access private * @static @@ -752,7 +715,7 @@ class Database extends AbstractData */ private static function _getDataType() { - return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'BLOB'); + return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'CLOB' : 'BLOB'); } /** @@ -772,7 +735,7 @@ class Database extends AbstractData /** * get the meta type, depending on the database driver * - * OCI can't even accept TEXT so it has to be VARCHAR2(200) + * OCI doesn't accept TEXT so it has to be VARCHAR2(4000) * * @access private * @static @@ -798,31 +761,17 @@ class Database extends AbstractData self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('paste') . ' ( ' . "dataid CHAR(16) NOT NULL$main_key, " . - "data $attachmentType, " . + "data $dataType, " . 'postdate INT, ' . 'expiredate INT, ' . 'opendiscussion INT, ' . 'burnafterreading INT, ' . "meta $metaType, " . "attachment $attachmentType, " . - "attachmentname $dataType$after_key )" . self::_getSemicolon() + "attachmentname $dataType$after_key )" ); } - /** - * get the nullable text type, depending on the database driver - * - * OCI will pad CHAR columns with spaces, hence VARCHAR2 - * - * @access private - * @static - * @return string - */ - private static function _getParentType() - { - return self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; - } - /** * create the paste table * @@ -833,21 +782,19 @@ class Database extends AbstractData { list($main_key, $after_key) = self::_getPrimaryKeyClauses(); $dataType = self::_getDataType(); - $parentType = self::_getParentType(); - $attachmentType = self::_getAttachmentType(); self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('comment') . ' ( ' . "dataid CHAR(16) NOT NULL$main_key, " . 'pasteid CHAR(16), ' . - "parentid $parentType, " . - "data $attachmentType, " . + 'parentid CHAR(16), ' . + "data $dataType, " . "nickname $dataType, " . "vizhash $dataType, " . - "postdate INT$after_key )" . self::_getSemicolon() + "postdate INT$after_key )" ); self::$_db->exec( - 'CREATE INDEX comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid)' . self::_getSemicolon() + 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . + self::_sanitizeIdentifier('comment') . '(pasteid)' ); } @@ -864,7 +811,7 @@ class Database extends AbstractData $textType = self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'TEXT'; self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('config') . - " ( id $charType NOT NULL$main_key, value $textType$after_key )" . self::_getSemicolon() + " ( id $charType NOT NULL$main_key, value $textType$after_key )" ); self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('config') . @@ -873,6 +820,24 @@ class Database extends AbstractData ); } + /** + * sanitizes CLOB values used with OCI + * + * From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field + * + * @access private + * @static + * @param int|string|resource $value + * @return int|string + */ + public static function _sanitizeClob($value) + { + if (is_resource($value)) { + $value = stream_get_contents($value); + } + return $value; + } + /** * sanitizes identifiers * @@ -883,21 +848,7 @@ class Database extends AbstractData */ private static function _sanitizeIdentifier($identifier) { - $id = preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); - return self::_sanitizeColumn($id); - } - - /** - * sanitizes column name because OCI - * - * @access private - * @static - * @param string $name - * @return string - */ - private static function _sanitizeColumn($name) - { - return self::$_type === 'oci' ? strtoupper($name) : $name; + return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); } /** @@ -915,43 +866,43 @@ class Database extends AbstractData case '0.21': // create the meta column if necessary (pre 0.21 change) try { - self::$_db->exec('SELECT meta FROM ' . self::_sanitizeIdentifier('paste') . ' LIMIT 1;'); + self::$_db->exec('SELECT meta FROM ' . self::_sanitizeIdentifier('paste') . ' LIMIT 1'); } catch (PDOException $e) { - self::$_db->exec('ALTER TABLE ' . self::_sanitizeIdentifier('paste') . ' ADD COLUMN meta TEXT;'); + self::$_db->exec('ALTER TABLE ' . self::_sanitizeIdentifier('paste') . ' ADD COLUMN meta TEXT'); } // SQLite only allows one ALTER statement at a time... self::$_db->exec( 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " ADD COLUMN attachment $attachmentType;" + " ADD COLUMN attachment $attachmentType" ); self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . " ADD COLUMN attachmentname $dataType;" + 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . " ADD COLUMN attachmentname $dataType" ); // SQLite doesn't support MODIFY, but it allows TEXT of similar // size as BLOB, so there is no need to change it there if (self::$_type !== 'sqlite') { self::$_db->exec( 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType;" + " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType" ); self::$_db->exec( 'ALTER TABLE ' . self::_sanitizeIdentifier('comment') . " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType, " . - "MODIFY COLUMN nickname $dataType, MODIFY COLUMN vizhash $dataType;" + "MODIFY COLUMN nickname $dataType, MODIFY COLUMN vizhash $dataType" ); } else { self::$_db->exec( 'CREATE UNIQUE INDEX IF NOT EXISTS paste_dataid ON ' . - self::_sanitizeIdentifier('paste') . '(dataid);' + self::_sanitizeIdentifier('paste') . '(dataid)' ); self::$_db->exec( 'CREATE UNIQUE INDEX IF NOT EXISTS comment_dataid ON ' . - self::_sanitizeIdentifier('comment') . '(dataid);' + self::_sanitizeIdentifier('comment') . '(dataid)' ); } self::$_db->exec( 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid);' + self::_sanitizeIdentifier('comment') . '(pasteid)' ); // no break, continue with updates for 0.22 and later case '1.3': @@ -961,7 +912,7 @@ class Database extends AbstractData if (self::$_type !== 'sqlite' && self::$_type !== 'pgsql') { self::$_db->exec( 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " MODIFY COLUMN data $attachmentType;" + " MODIFY COLUMN data $attachmentType" ); } // no break, continue with updates for all newer versions @@ -973,25 +924,4 @@ class Database extends AbstractData ); } } - - /** - * read CLOB for OCI - * https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field - * - * @access private - * @static - * @param resource $column - * @return string - */ - private static function _clob($column) - { - if ($column == null) { - return null; - } - $str = ''; - while ($tmp = fread($column, 1024)) { - $str .= $tmp; - } - return $str; - } } diff --git a/tst/Data/DatabaseTest.php b/tst/Data/DatabaseTest.php index 1cfc0be..c433b5a 100644 --- a/tst/Data/DatabaseTest.php +++ b/tst/Data/DatabaseTest.php @@ -388,4 +388,16 @@ class DatabaseTest extends PHPUnit_Framework_TestCase $this->assertEquals(Controller::VERSION, $result['value']); Helper::rmDir($this->_path); } + + public function testOciClob() + { + $int = (int) random_bytes(1); + $string = random_bytes(10); + $clob = fopen('php://memory', 'r+'); + fwrite($clob, $string); + rewind($clob); + $this->assertEquals($int, Database::_sanitizeClob($int)); + $this->assertEquals($string, Database::_sanitizeClob($string)); + $this->assertEquals($string, Database::_sanitizeClob($clob)); + } } From 585d5db983d3a2e25197c548c2e77d0a0fbb3061 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 Jan 2022 08:47:34 +0100 Subject: [PATCH 192/478] apply StyleCI recommendation --- tst/Data/DatabaseTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tst/Data/DatabaseTest.php b/tst/Data/DatabaseTest.php index c433b5a..16e6fcb 100644 --- a/tst/Data/DatabaseTest.php +++ b/tst/Data/DatabaseTest.php @@ -391,9 +391,9 @@ class DatabaseTest extends PHPUnit_Framework_TestCase public function testOciClob() { - $int = (int) random_bytes(1); + $int = (int) random_bytes(1); $string = random_bytes(10); - $clob = fopen('php://memory', 'r+'); + $clob = fopen('php://memory', 'r+'); fwrite($clob, $string); rewind($clob); $this->assertEquals($int, Database::_sanitizeClob($int)); From c725b4f0feb2f44cc46cc67b3236d29078fe8716 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 Jan 2022 21:29:39 +0100 Subject: [PATCH 193/478] handle 'IF NOT EXISTS' differently in OCI --- lib/Data/Database.php | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index f616a96..59c7017 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -792,10 +792,24 @@ class Database extends AbstractData "vizhash $dataType, " . "postdate INT$after_key )" ); - self::$_db->exec( - 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid)' - ); + if (self::$_type === 'oci') { + self::$_db->exec( + 'declare + already_exists exception; + columns_indexed exception; + pragma exception_init( already_exists, -955 ); + pragma exception_init(columns_indexed, -1408); + begin + execute immediate \'create index comment_parent on ' . self::_sanitizeIdentifier('comment') . ' (pasteid)\'; + exception + end' + ); + } else { + self::$_db->exec( + 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . + self::_sanitizeIdentifier('comment') . '(pasteid)' + ); + } } /** From 35ef64ff7989839ebe4e3083f82d2317ce07c001 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 Jan 2022 22:11:49 +0100 Subject: [PATCH 194/478] remove duplication, kudos @rugk --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 59c7017..7dc5674 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -822,7 +822,7 @@ class Database extends AbstractData { list($main_key, $after_key) = self::_getPrimaryKeyClauses('id'); $charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; - $textType = self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'TEXT'; + $textType = self::_getMetaType(); self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('config') . " ( id $charType NOT NULL$main_key, value $textType$after_key )" From 83336b294904ff808b351ee449396482c47ce4a4 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 07:11:06 +0100 Subject: [PATCH 195/478] documented changes for Postgres and Oracle --- INSTALL.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 0e72823..da6812e 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -190,8 +190,14 @@ CREATE TABLE prefix_config ( INSERT INTO prefix_config VALUES('VERSION', '1.3.5'); ``` -In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to -be TEXT and not BLOB or MEDIUMBLOB. +In **PostgreSQL**, the `data`, `attachment`, `nickname` and `vizhash` columns +need to be `TEXT` and not `BLOB` or `MEDIUMBLOB`. The key names in brackets, +after `PRIMARY KEY`, need to be removed. + +In **Oracle**, the `data`, `attachment`, `nickname` and `vizhash` columns need +to be `CLOB` and not `BLOB` or `MEDIUMBLOB`, the `id` column in the `config` +table needs to be `VARCHAR2(16)` and the `meta` column in the `paste` table +and the `value` column in the `config` table need to be `VARCHAR2(4000)`. ### Using Google Cloud Storage If you want to deploy PrivateBin in a serverless manner in the Google Cloud, you From 47deaeb7ca960def189f232f3a79db53eb33494e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 07:11:36 +0100 Subject: [PATCH 196/478] use the correct function --- lib/Data/Database.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 7dc5674..d725bb3 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -548,11 +548,11 @@ class Database extends AbstractData foreach ($params as $key => $parameter) { $position = $key + 1; if (is_int($parameter)) { - $statement->bindParam($position, $parameter, PDO::PARAM_INT); + $statement->bindValue($position, $parameter, PDO::PARAM_INT); } elseif ($length = strlen($parameter) >= 4000) { - $statement->bindParam($position, $parameter, PDO::PARAM_STR, $length); + $statement->bindValue($position, $parameter, PDO::PARAM_STR, $length); } else { - $statement->bindParam($position, $parameter); + $statement->bindValue($position, $parameter); } } $result = $statement->execute(); From b54308a77eb7baed177030e54d4d4379dac88c53 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 07:19:35 +0100 Subject: [PATCH 197/478] don't mangle non-arrays --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index d725bb3..a0694b0 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -582,7 +582,7 @@ class Database extends AbstractData $statement->fetch(PDO::FETCH_ASSOC) : $statement->fetchAll(PDO::FETCH_ASSOC); $statement->closeCursor(); - if (self::$_type === 'oci') { + if (self::$_type === 'oci' && is_array($result)) { // returned column names are all upper case, convert these back // returned CLOB values are streams, convert these into strings $result = array_combine( From b133c2e2330927312206c3566072eadf25ee807e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 07:32:28 +0100 Subject: [PATCH 198/478] sanitize both single rows and multiple ones --- lib/Data/Database.php | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index a0694b0..8c73a20 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -585,10 +585,9 @@ class Database extends AbstractData if (self::$_type === 'oci' && is_array($result)) { // returned column names are all upper case, convert these back // returned CLOB values are streams, convert these into strings - $result = array_combine( - array_map('strtolower', array_keys($result)), - array_map('self::_sanitizeClob', array_values($result)) - ); + $result = $firstOnly ? + self::_sanitizeOciRow($result) : + array_map('self::_sanitizeOciRow', $result); } return $result; } @@ -839,7 +838,7 @@ class Database extends AbstractData * * From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field * - * @access private + * @access public * @static * @param int|string|resource $value * @return int|string @@ -865,6 +864,22 @@ class Database extends AbstractData return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); } + /** + * sanitizes row returned by OCI + * + * @access private + * @static + * @param array $row + * @return array + */ + private static function _sanitizeOciRow($row) + { + return array_combine( + array_map('strtolower', array_keys($row)), + array_map('self::_sanitizeClob', array_values($row)) + ); + } + /** * upgrade the database schema from an old version * From 0be55e05bf45c5761360e8f6bd1c224ba1b9fbac Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 20:59:02 +0100 Subject: [PATCH 199/478] use quoted identifiers, tell MySQL to expect ANSI SQL --- lib/Data/Database.php | 203 ++++++++++++++++++++---------------------- 1 file changed, 98 insertions(+), 105 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 8c73a20..d0616a7 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -97,6 +97,11 @@ class Database extends AbstractData self::$_type = strtolower( substr($options['dsn'], 0, strpos($options['dsn'], ':')) ); + // MySQL uses backticks to quote identifiers by default, + // tell it to expect ANSI SQL double quotes + if (self::$_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) { + $options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET sql_mode='ANSI_QUOTES'"; + } $tableQuery = self::_getTableQuery(self::$_type); self::$_db = new PDO( $options['dsn'], @@ -200,8 +205,8 @@ class Database extends AbstractData } try { return self::_exec( - 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . - ' VALUES(?,?,?,?,?,?,?,?,?)', + 'INSERT INTO "' . self::_sanitizeIdentifier('paste') . + '" VALUES(?,?,?,?,?,?,?,?,?)', array( $pasteid, $isVersion1 ? $paste['data'] : Json::encode($paste), @@ -235,8 +240,8 @@ class Database extends AbstractData self::$_cache[$pasteid] = false; try { $paste = self::_select( - 'SELECT * FROM ' . self::_sanitizeIdentifier('paste') . - ' WHERE dataid = ?', array($pasteid), true + 'SELECT * FROM "' . self::_sanitizeIdentifier('paste') . + '" WHERE "dataid" = ?', array($pasteid), true ); } catch (Exception $e) { $paste = false; @@ -297,12 +302,12 @@ class Database extends AbstractData public function delete($pasteid) { self::_exec( - 'DELETE FROM ' . self::_sanitizeIdentifier('paste') . - ' WHERE dataid = ?', array($pasteid) + 'DELETE FROM "' . self::_sanitizeIdentifier('paste') . + '" WHERE "dataid" = ?', array($pasteid) ); self::_exec( - 'DELETE FROM ' . self::_sanitizeIdentifier('comment') . - ' WHERE pasteid = ?', array($pasteid) + 'DELETE FROM "' . self::_sanitizeIdentifier('comment') . + '" WHERE "pasteid" = ?', array($pasteid) ); if ( array_key_exists($pasteid, self::$_cache) @@ -357,8 +362,8 @@ class Database extends AbstractData } try { return self::_exec( - 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . - ' VALUES(?,?,?,?,?,?,?)', + 'INSERT INTO "' . self::_sanitizeIdentifier('comment') . + '" VALUES(?,?,?,?,?,?,?)', array( $commentid, $pasteid, @@ -384,8 +389,8 @@ class Database extends AbstractData public function readComments($pasteid) { $rows = self::_select( - 'SELECT * FROM ' . self::_sanitizeIdentifier('comment') . - ' WHERE pasteid = ?', array($pasteid) + 'SELECT * FROM "' . self::_sanitizeIdentifier('comment') . + '" WHERE "pasteid" = ?', array($pasteid) ); // create comment list @@ -429,8 +434,8 @@ class Database extends AbstractData { try { return (bool) self::_select( - 'SELECT dataid FROM ' . self::_sanitizeIdentifier('comment') . - ' WHERE pasteid = ? AND parentid = ? AND dataid = ?', + 'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('comment') . + '" WHERE "pasteid" = ? AND "parentid" = ? AND "dataid" = ?', array($pasteid, $parentid, $commentid), true ); } catch (Exception $e) { @@ -458,8 +463,8 @@ class Database extends AbstractData } } return self::_exec( - 'UPDATE ' . self::_sanitizeIdentifier('config') . - ' SET value = ? WHERE id = ?', + 'UPDATE "' . self::_sanitizeIdentifier('config') . + '" SET "value" = ? WHERE "id" = ?', array($value, strtoupper($namespace)) ); } @@ -479,8 +484,8 @@ class Database extends AbstractData if ($value === '') { // initialize the row, so that setValue can rely on UPDATE queries self::_exec( - 'INSERT INTO ' . self::_sanitizeIdentifier('config') . - ' VALUES(?,?)', + 'INSERT INTO "' . self::_sanitizeIdentifier('config') . + '" VALUES(?,?)', array($configKey, '') ); @@ -517,8 +522,8 @@ class Database extends AbstractData { $pastes = array(); $rows = self::_select( - 'SELECT dataid FROM ' . self::_sanitizeIdentifier('paste') . - ' WHERE expiredate < ? AND expiredate != ? ' . + 'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('paste') . + '" WHERE "expiredate" < ? AND "expiredate" != ? ' . (self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'), array(time(), 0, $batchsize) ); @@ -586,8 +591,12 @@ class Database extends AbstractData // returned column names are all upper case, convert these back // returned CLOB values are streams, convert these into strings $result = $firstOnly ? - self::_sanitizeOciRow($result) : - array_map('self::_sanitizeOciRow', $result); + array_map('self::_sanitizeClob', $result) : + array_map( + function ($row) { + return array_map('self::_sanitizeClob', $row); + }, $result + ); } return $result; } @@ -621,39 +630,39 @@ class Database extends AbstractData { switch ($type) { case 'ibm': - $sql = 'SELECT tabname FROM SYSCAT.TABLES '; + $sql = 'SELECT "tabname" FROM "SYSCAT"."TABLES"'; break; case 'informix': - $sql = 'SELECT tabname FROM systables '; + $sql = 'SELECT "tabname" FROM "systables"'; break; case 'mssql': - $sql = 'SELECT name FROM sysobjects ' - . "WHERE type = 'U' ORDER BY name"; + $sql = 'SELECT "name" FROM "sysobjects" ' + . 'WHERE "type" = \'U\' ORDER BY "name"'; break; case 'mysql': $sql = 'SHOW TABLES'; break; case 'oci': - $sql = 'SELECT table_name FROM all_tables'; + $sql = 'SELECT "table_name" FROM "all_tables"'; break; case 'pgsql': - $sql = 'SELECT c.relname AS table_name ' - . 'FROM pg_class c, pg_user u ' - . "WHERE c.relowner = u.usesysid AND c.relkind = 'r' " - . 'AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) ' - . "AND c.relname !~ '^(pg_|sql_)' " + $sql = 'SELECT c."relname" AS "table_name" ' + . 'FROM "pg_class" c, "pg_user" u ' + . "WHERE c.\"relowner\" = u.\"usesysid\" AND c.\"relkind\" = 'r' " + . 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") ' + . "AND c.\"relname\" !~ '^(pg_|sql_)' " . 'UNION ' - . 'SELECT c.relname AS table_name ' - . 'FROM pg_class c ' - . "WHERE c.relkind = 'r' " - . 'AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) ' - . 'AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) ' - . "AND c.relname !~ '^pg_'"; + . 'SELECT c."relname" AS "table_name" ' + . 'FROM "pg_class" c ' + . "WHERE c.\"relkind\" = 'r' " + . 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") ' + . 'AND NOT EXISTS (SELECT 1 FROM "pg_user" WHERE "usesysid" = c."relowner") ' + . "AND c.\"relname\" !~ '^pg_'"; break; case 'sqlite': - $sql = "SELECT name FROM sqlite_master WHERE type='table' " - . 'UNION ALL SELECT name FROM sqlite_temp_master ' - . "WHERE type='table' ORDER BY name"; + $sql = 'SELECT "name" FROM "sqlite_master" WHERE "type"=\'table\' ' + . 'UNION ALL SELECT "name" FROM "sqlite_temp_master" ' + . 'WHERE "type"=\'table\' ORDER BY "name"'; break; default: throw new Exception( @@ -675,8 +684,8 @@ class Database extends AbstractData { try { $row = self::_select( - 'SELECT value FROM ' . self::_sanitizeIdentifier('config') . - ' WHERE id = ?', array($key), true + 'SELECT "value" FROM "' . self::_sanitizeIdentifier('config') . + '" WHERE "id" = ?', array($key), true ); } catch (PDOException $e) { return ''; @@ -696,7 +705,7 @@ class Database extends AbstractData { $main_key = $after_key = ''; if (self::$_type === 'mysql' || self::$_type === 'oci') { - $after_key = ", PRIMARY KEY ($key)"; + $after_key = ", PRIMARY KEY (\"$key\")"; } else { $main_key = ' PRIMARY KEY'; } @@ -758,16 +767,16 @@ class Database extends AbstractData $attachmentType = self::_getAttachmentType(); $metaType = self::_getMetaType(); self::$_db->exec( - 'CREATE TABLE ' . self::_sanitizeIdentifier('paste') . ' ( ' . - "dataid CHAR(16) NOT NULL$main_key, " . - "data $dataType, " . - 'postdate INT, ' . - 'expiredate INT, ' . - 'opendiscussion INT, ' . - 'burnafterreading INT, ' . - "meta $metaType, " . - "attachment $attachmentType, " . - "attachmentname $dataType$after_key )" + 'CREATE TABLE "' . self::_sanitizeIdentifier('paste') . '" ( ' . + "\"dataid\" CHAR(16) NOT NULL$main_key, " . + "\"data\" $dataType, " . + '"postdate" INT, ' . + '"expiredate" INT, ' . + '"opendiscussion" INT, ' . + '"burnafterreading" INT, ' . + "\"meta\" $metaType, " . + "\"attachment\" $attachmentType, " . + "\"attachmentname\" $dataType$after_key )" ); } @@ -782,14 +791,14 @@ class Database extends AbstractData list($main_key, $after_key) = self::_getPrimaryKeyClauses(); $dataType = self::_getDataType(); self::$_db->exec( - 'CREATE TABLE ' . self::_sanitizeIdentifier('comment') . ' ( ' . - "dataid CHAR(16) NOT NULL$main_key, " . - 'pasteid CHAR(16), ' . - 'parentid CHAR(16), ' . - "data $dataType, " . - "nickname $dataType, " . - "vizhash $dataType, " . - "postdate INT$after_key )" + 'CREATE TABLE "' . self::_sanitizeIdentifier('comment') . '" ( ' . + "\"dataid\" CHAR(16) NOT NULL$main_key, " . + '"pasteid" CHAR(16), ' . + '"parentid" CHAR(16), ' . + "\"data\" $dataType, " . + "\"nickname\" $dataType, " . + "\"vizhash\" $dataType, " . + "\"postdate\" INT$after_key )" ); if (self::$_type === 'oci') { self::$_db->exec( @@ -799,14 +808,14 @@ class Database extends AbstractData pragma exception_init( already_exists, -955 ); pragma exception_init(columns_indexed, -1408); begin - execute immediate \'create index comment_parent on ' . self::_sanitizeIdentifier('comment') . ' (pasteid)\'; + execute immediate \'create index "comment_parent" on "' . self::_sanitizeIdentifier('comment') . '" ("pasteid")\'; exception end' ); } else { self::$_db->exec( - 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid)' + 'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' . + self::_sanitizeIdentifier('comment') . '" ("pasteid")' ); } } @@ -823,12 +832,12 @@ class Database extends AbstractData $charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; $textType = self::_getMetaType(); self::$_db->exec( - 'CREATE TABLE ' . self::_sanitizeIdentifier('config') . - " ( id $charType NOT NULL$main_key, value $textType$after_key )" + 'CREATE TABLE "' . self::_sanitizeIdentifier('config') . + "\" ( \"id\" $charType NOT NULL$main_key, \"value\" $textType$after_key )" ); self::_exec( - 'INSERT INTO ' . self::_sanitizeIdentifier('config') . - ' VALUES(?,?)', + 'INSERT INTO "' . self::_sanitizeIdentifier('config') . + '" VALUES(?,?)', array('VERSION', Controller::VERSION) ); } @@ -864,22 +873,6 @@ class Database extends AbstractData return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); } - /** - * sanitizes row returned by OCI - * - * @access private - * @static - * @param array $row - * @return array - */ - private static function _sanitizeOciRow($row) - { - return array_combine( - array_map('strtolower', array_keys($row)), - array_map('self::_sanitizeClob', array_values($row)) - ); - } - /** * upgrade the database schema from an old version * @@ -895,43 +888,43 @@ class Database extends AbstractData case '0.21': // create the meta column if necessary (pre 0.21 change) try { - self::$_db->exec('SELECT meta FROM ' . self::_sanitizeIdentifier('paste') . ' LIMIT 1'); + self::$_db->exec('SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" LIMIT 1'); } catch (PDOException $e) { - self::$_db->exec('ALTER TABLE ' . self::_sanitizeIdentifier('paste') . ' ADD COLUMN meta TEXT'); + self::$_db->exec('ALTER TABLE "' . self::_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT'); } // SQLite only allows one ALTER statement at a time... self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " ADD COLUMN attachment $attachmentType" + 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . + "\" ADD COLUMN \"attachment\" $attachmentType" ); self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . " ADD COLUMN attachmentname $dataType" + 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . "\" ADD COLUMN \"attachmentname\" $dataType" ); // SQLite doesn't support MODIFY, but it allows TEXT of similar // size as BLOB, so there is no need to change it there if (self::$_type !== 'sqlite') { self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType" + 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . + "\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType" ); self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('comment') . - " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType, " . - "MODIFY COLUMN nickname $dataType, MODIFY COLUMN vizhash $dataType" + 'ALTER TABLE "' . self::_sanitizeIdentifier('comment') . + "\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType, " . + "MODIFY COLUMN \"nickname\" $dataType, MODIFY COLUMN \"vizhash\" $dataType" ); } else { self::$_db->exec( - 'CREATE UNIQUE INDEX IF NOT EXISTS paste_dataid ON ' . - self::_sanitizeIdentifier('paste') . '(dataid)' + 'CREATE UNIQUE INDEX IF NOT EXISTS "paste_dataid" ON "' . + self::_sanitizeIdentifier('paste') . '" ("dataid")' ); self::$_db->exec( - 'CREATE UNIQUE INDEX IF NOT EXISTS comment_dataid ON ' . - self::_sanitizeIdentifier('comment') . '(dataid)' + 'CREATE UNIQUE INDEX IF NOT EXISTS "comment_dataid" ON "' . + self::_sanitizeIdentifier('comment') . '" ("dataid")' ); } self::$_db->exec( - 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid)' + 'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' . + self::_sanitizeIdentifier('comment') . '" ("pasteid")' ); // no break, continue with updates for 0.22 and later case '1.3': @@ -940,15 +933,15 @@ class Database extends AbstractData // to change it there if (self::$_type !== 'sqlite' && self::$_type !== 'pgsql') { self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " MODIFY COLUMN data $attachmentType" + 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . + "\" MODIFY COLUMN \"data\" $attachmentType" ); } // no break, continue with updates for all newer versions default: self::_exec( - 'UPDATE ' . self::_sanitizeIdentifier('config') . - ' SET value = ? WHERE id = ?', + 'UPDATE "' . self::_sanitizeIdentifier('config') . + '" SET "value" = ? WHERE "id" = ?', array(Controller::VERSION, 'VERSION') ); } From 8d6392192428d6a0099fa0cc29ec8c52d7c99deb Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 21:24:28 +0100 Subject: [PATCH 200/478] workaround bug in OCI PDO driver --- lib/Data/Database.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index d0616a7..1e9adea 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -583,9 +583,17 @@ class Database extends AbstractData { $statement = self::$_db->prepare($sql); $statement->execute($params); - $result = $firstOnly ? - $statement->fetch(PDO::FETCH_ASSOC) : - $statement->fetchAll(PDO::FETCH_ASSOC); + if ($firstOnly) { + $result = $statement->fetch(PDO::FETCH_ASSOC); + } elseif (self::$_type === 'oci') { + // workaround for https://bugs.php.net/bug.php?id=46728 + $result = array(); + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + } else { + $result = $statement->fetchAll(PDO::FETCH_ASSOC); + } $statement->closeCursor(); if (self::$_type === 'oci' && is_array($result)) { // returned column names are all upper case, convert these back From 4f051fe5a5d7891e0d3d2f69ea9787ea637114f8 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 21:31:40 +0100 Subject: [PATCH 201/478] revert regression --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 1e9adea..1441648 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -777,7 +777,7 @@ class Database extends AbstractData self::$_db->exec( 'CREATE TABLE "' . self::_sanitizeIdentifier('paste') . '" ( ' . "\"dataid\" CHAR(16) NOT NULL$main_key, " . - "\"data\" $dataType, " . + "\"data\" $attachmentType, " . '"postdate" INT, ' . '"expiredate" INT, ' . '"opendiscussion" INT, ' . From 0cc2b677538ac948132c1ee674c433ccdf104640 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 21:45:22 +0100 Subject: [PATCH 202/478] bindValue doesn't need the length --- lib/Data/Database.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 1441648..583c400 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -395,7 +395,7 @@ class Database extends AbstractData // create comment list $comments = array(); - if (count($rows)) { + if (is_array($rows) && count($rows)) { foreach ($rows as $row) { $i = $this->getOpenSlot($comments, (int) $row['postdate']); $data = Json::decode($row['data']); @@ -527,7 +527,7 @@ class Database extends AbstractData (self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'), array(time(), 0, $batchsize) ); - if (count($rows)) { + if (is_array($rows) && count($rows)) { foreach ($rows as $row) { $pastes[] = $row['dataid']; } @@ -554,8 +554,6 @@ class Database extends AbstractData $position = $key + 1; if (is_int($parameter)) { $statement->bindValue($position, $parameter, PDO::PARAM_INT); - } elseif ($length = strlen($parameter) >= 4000) { - $statement->bindValue($position, $parameter, PDO::PARAM_STR, $length); } else { $statement->bindValue($position, $parameter); } From a8e1c33b54ed612fd7fdbdb00a2663ee1ebdb9f3 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 17:26:09 +0100 Subject: [PATCH 203/478] stick to single convention of binding parameters --- lib/Data/Database.php | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 583c400..92aeec7 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -548,20 +548,15 @@ class Database extends AbstractData private static function _exec($sql, array $params) { $statement = self::$_db->prepare($sql); - if (self::$_type === 'oci') { - // It is not possible to execute in the normal way if strlen($param) >= 4000 - foreach ($params as $key => $parameter) { - $position = $key + 1; - if (is_int($parameter)) { - $statement->bindValue($position, $parameter, PDO::PARAM_INT); - } else { - $statement->bindValue($position, $parameter); - } + foreach ($params as $key => $parameter) { + $position = $key + 1; + if (is_int($parameter)) { + $statement->bindValue($position, $parameter, PDO::PARAM_INT); + } else { + $statement->bindValue($position, $parameter); } - $result = $statement->execute(); - } else { - $result = $statement->execute($params); } + $result = $statement->execute(); $statement->closeCursor(); return $result; } From 56c54dd8800399b16403ff9901e8a3048811a7aa Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 17:48:27 +0100 Subject: [PATCH 204/478] prefer switch statements for complex logic, all comparing the same variable --- lib/Data/Database.php | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 92aeec7..27e71c9 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -705,10 +705,14 @@ class Database extends AbstractData private static function _getPrimaryKeyClauses($key = 'dataid') { $main_key = $after_key = ''; - if (self::$_type === 'mysql' || self::$_type === 'oci') { - $after_key = ", PRIMARY KEY (\"$key\")"; - } else { - $main_key = ' PRIMARY KEY'; + switch (self::$_type) { + case 'mysql': + case 'oci': + $after_key = ", PRIMARY KEY (\"$key\")"; + break; + default: + $main_key = ' PRIMARY KEY'; + break; } return array($main_key, $after_key); } @@ -724,7 +728,14 @@ class Database extends AbstractData */ private static function _getDataType() { - return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'CLOB' : 'BLOB'); + switch (self::$_type) { + case 'oci': + return 'CLOB'; + case 'pgsql': + return 'TEXT'; + default: + return 'BLOB'; + } } /** @@ -738,7 +749,14 @@ class Database extends AbstractData */ private static function _getAttachmentType() { - return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'CLOB' : 'MEDIUMBLOB'); + switch (self::$_type) { + case 'oci': + return 'CLOB'; + case 'pgsql': + return 'TEXT'; + default: + return 'MEDIUMBLOB'; + } } /** @@ -752,7 +770,12 @@ class Database extends AbstractData */ private static function _getMetaType() { - return self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'TEXT'; + switch (self::$_type) { + case 'oci': + return 'VARCHAR2(4000)'; + default: + return 'TEXT'; + } } /** From 0b6af67b99151905541e5dad65246051192f3bc8 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 17:50:24 +0100 Subject: [PATCH 205/478] removed obsolete comment --- lib/Data/Database.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 27e71c9..080da53 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -589,7 +589,6 @@ class Database extends AbstractData } $statement->closeCursor(); if (self::$_type === 'oci' && is_array($result)) { - // returned column names are all upper case, convert these back // returned CLOB values are streams, convert these into strings $result = $firstOnly ? array_map('self::_sanitizeClob', $result) : From b8e8755fb1f70e5259323e2dcb16de49fefb1dba Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 21:36:18 +0100 Subject: [PATCH 206/478] Basically it wants a non-empty catch statement Co-authored-by: Austin Huang --- lib/Data/Database.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 080da53..c9901dc 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -833,7 +833,9 @@ class Database extends AbstractData begin execute immediate \'create index "comment_parent" on "' . self::_sanitizeIdentifier('comment') . '" ("pasteid")\'; exception - end' + when already_exists or columns_indexed then + NULL; + end;' ); } else { self::$_db->exec( From 0c4852c099cd1eb9015cdfac31cc7613b10c5b82 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 21:40:10 +0100 Subject: [PATCH 207/478] this fixes the comment display issue Co-authored-by: Austin Huang --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index c9901dc..8dcbe0c 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -582,7 +582,7 @@ class Database extends AbstractData // workaround for https://bugs.php.net/bug.php?id=46728 $result = array(); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $result[] = $row; + $result[] = array_map('self::_sanitizeClob', $row); } } else { $result = $statement->fetchAll(PDO::FETCH_ASSOC); From 535f038daaa1b558e3ae8f3093481c499d190635 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 21:43:31 +0100 Subject: [PATCH 208/478] handle `LIMIT` in oci Co-authored-by: Austin Huang --- lib/Data/Database.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 8dcbe0c..aed8db6 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -913,7 +913,10 @@ class Database extends AbstractData case '0.21': // create the meta column if necessary (pre 0.21 change) try { - self::$_db->exec('SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" LIMIT 1'); + self::$_db->exec( + 'SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" ' . + (self::$_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1') + ); } catch (PDOException $e) { self::$_db->exec('ALTER TABLE "' . self::_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT'); } From 55db9426b99bd7514fa2bb12b29c15a9c379d7bf Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 21:43:48 +0100 Subject: [PATCH 209/478] Throws `ORA-00942: table or view does not exist` otherwise Co-authored-by: Austin Huang --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index aed8db6..657107b 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -643,7 +643,7 @@ class Database extends AbstractData $sql = 'SHOW TABLES'; break; case 'oci': - $sql = 'SELECT "table_name" FROM "all_tables"'; + $sql = 'SELECT table_name FROM all_tables'; break; case 'pgsql': $sql = 'SELECT c."relname" AS "table_name" ' From f4438a01036bb53e42b879a6da6e6f63052453d6 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 21:44:20 +0100 Subject: [PATCH 210/478] inserting CLOB absolutely requires a length argument Co-authored-by: Austin Huang --- lib/Data/Database.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 657107b..0e60598 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -548,12 +548,13 @@ class Database extends AbstractData private static function _exec($sql, array $params) { $statement = self::$_db->prepare($sql); - foreach ($params as $key => $parameter) { - $position = $key + 1; + foreach ($params as $key => &$parameter) { if (is_int($parameter)) { - $statement->bindValue($position, $parameter, PDO::PARAM_INT); + $statement->bindParam($key + 1, $parameter, PDO::PARAM_INT); + } elseif (strlen($parameter) >= 4000) { + $statement->bindParam($key + 1, $parameter, PDO::PARAM_STR, strlen($parameter)); } else { - $statement->bindValue($position, $parameter); + $statement->bindParam($key + 1, $parameter); } } $result = $statement->execute(); From 0333777a37c77cd9540962d231306b0a09a6df12 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 25 Jan 2022 05:59:22 +0100 Subject: [PATCH 211/478] remove duplicate CLOB sanitation --- lib/Data/Database.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 0e60598..3a9a806 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -593,11 +593,7 @@ class Database extends AbstractData // returned CLOB values are streams, convert these into strings $result = $firstOnly ? array_map('self::_sanitizeClob', $result) : - array_map( - function ($row) { - return array_map('self::_sanitizeClob', $row); - }, $result - ); + $result; } return $result; } From 53c0e4976bf2321e4da0205e95a068c1652f98d5 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 26 Jan 2022 05:26:47 +0100 Subject: [PATCH 212/478] document what the U type stands for --- lib/Data/Database.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 3a9a806..babcd25 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -633,6 +633,7 @@ class Database extends AbstractData $sql = 'SELECT "tabname" FROM "systables"'; break; case 'mssql': + // U: tables created by the user $sql = 'SELECT "name" FROM "sysobjects" ' . 'WHERE "type" = \'U\' ORDER BY "name"'; break; From 1d20eee1693a67a6ce71d50458122e0f6ebbe915 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 26 Jan 2022 05:28:29 +0100 Subject: [PATCH 213/478] readability --- lib/Data/Database.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index babcd25..a35726c 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -549,12 +549,13 @@ class Database extends AbstractData { $statement = self::$_db->prepare($sql); foreach ($params as $key => &$parameter) { + $position = $key + 1; if (is_int($parameter)) { - $statement->bindParam($key + 1, $parameter, PDO::PARAM_INT); + $statement->bindParam($position, $parameter, PDO::PARAM_INT); } elseif (strlen($parameter) >= 4000) { - $statement->bindParam($key + 1, $parameter, PDO::PARAM_STR, strlen($parameter)); + $statement->bindParam($position, $parameter, PDO::PARAM_STR, strlen($parameter)); } else { - $statement->bindParam($key + 1, $parameter); + $statement->bindParam($position, $parameter); } } $result = $statement->execute(); From d43dfdefdf5188b62d8ee028c8a0fad68a3ac6c3 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 30 Jan 2022 21:21:23 +0100 Subject: [PATCH 214/478] New translations en.json (Finnish) --- i18n/fi.json | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 i18n/fi.json diff --git a/i18n/fi.json b/i18n/fi.json new file mode 100644 index 0000000..a96bab5 --- /dev/null +++ b/i18n/fi.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 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.", + "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", + "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", + "Please wait %d seconds between each post.": [ + "Please wait %d second between each post. (singular)", + "Please wait %d seconds between each post. (1st plural)", + "Please wait %d seconds between each post. (2nd plural)", + "Please wait %d seconds between each post. (3rd plural)" + ], + "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", + "Invalid data.": "Invalid data.", + "You are unlucky. Try again.": "You are unlucky. Try again.", + "Error saving comment. Sorry.": "Error saving comment. Sorry.", + "Error saving paste. Sorry.": "Error saving paste. Sorry.", + "Invalid paste ID.": "Invalid paste ID.", + "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", + "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", + "Paste was properly deleted.": "Paste was properly deleted.", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", + "%s requires a modern browser to work.": "%s requires a modern browser to work.", + "New": "New", + "Send": "Send", + "Clone": "Clone", + "Raw text": "Raw text", + "Expires": "Expires", + "Burn after reading": "Burn after reading", + "Open discussion": "Open discussion", + "Password (recommended)": "Password (recommended)", + "Discussion": "Discussion", + "Toggle navigation": "Toggle navigation", + "%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" +} From 29ffd25c181352a327e0dd99d512f394b88dc3b9 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 30 Jan 2022 21:42:24 +0100 Subject: [PATCH 215/478] apply suggestion of @r4sas --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index a35726c..03e6061 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -647,7 +647,7 @@ class Database extends AbstractData case 'pgsql': $sql = 'SELECT c."relname" AS "table_name" ' . 'FROM "pg_class" c, "pg_user" u ' - . "WHERE c.\"relowner\" = u.\"usesysid\" AND c.\"relkind\" = 'r' " + . 'WHERE c."relowner" = u."usesysid" AND c."relkind" = \'r\' ' . 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") ' . "AND c.\"relname\" !~ '^(pg_|sql_)' " . 'UNION ' From 1b2b183d7fa799fbc947a7bc3ce41078693b35fe Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 31 Jan 2022 10:02:10 +0100 Subject: [PATCH 216/478] New translations en.json (Finnish) --- i18n/fi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/fi.json b/i18n/fi.json index a96bab5..e46b5bb 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -3,7 +3,7 @@ "%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", + "en": "fi", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", From 1a7ac272eb32c6bf41ddf4a3602e972a12831b70 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 31 Jan 2022 10:59:17 +0100 Subject: [PATCH 217/478] New translations en.json (Finnish) --- i18n/fi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/fi.json b/i18n/fi.json index e46b5bb..9d0e93a 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -1,7 +1,7 @@ { "PrivateBin": "PrivateBin", - "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s 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.", + "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s on minimalistinen, avoimen lähdekoodin online pastebin jossa palvelimella ei ole tietoa syötetystä datasta. Data salataan/puretaan %sselaimessa%s käyttäen 256-bittistä AES:ää.", + "More information on the project page.": "Enemmän tietoa projektisivulla.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "fi", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", From 57b03d438e330de1d2bce3b7d095002c7a8509aa Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 31 Jan 2022 12:35:57 +0100 Subject: [PATCH 218/478] New translations en.json (Finnish) --- i18n/fi.json | 212 +++++++++++++++++++++++++-------------------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/i18n/fi.json b/i18n/fi.json index 9d0e93a..0dd27d5 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -4,8 +4,8 @@ "More information on the project page.": "Enemmän tietoa projektisivulla.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "fi", - "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.", + "Paste does not exist, has expired or has been deleted.": "Pastea ei ole olemassa, se on vanhentunut, tai se on poistettu.", + "%s requires php %s or above to work. Sorry.": "%s tarvitsee php %s-versiota tai uudempaa toimiakseen. Anteeksi.", "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", "Please wait %d seconds between each post.": [ "Please wait %d second between each post. (singular)", @@ -13,122 +13,122 @@ "Please wait %d seconds between each post. (2nd plural)", "Please wait %d seconds between each post. (3rd plural)" ], - "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", - "Invalid data.": "Invalid data.", - "You are unlucky. Try again.": "You are unlucky. Try again.", - "Error saving comment. Sorry.": "Error saving comment. Sorry.", - "Error saving paste. Sorry.": "Error saving paste. Sorry.", - "Invalid paste ID.": "Invalid paste ID.", - "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", - "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", - "Paste was properly deleted.": "Paste was properly deleted.", - "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", - "%s requires a modern browser to work.": "%s requires a modern browser to work.", - "New": "New", - "Send": "Send", - "Clone": "Clone", - "Raw text": "Raw text", - "Expires": "Expires", - "Burn after reading": "Burn after reading", + "Paste is limited to %s of encrypted data.": "Paste on rajoitettu kokoon %s salattua dataa.", + "Invalid data.": "Virheellinen data.", + "You are unlucky. Try again.": "Olet epäonnekas. Yritä uudelleen", + "Error saving comment. Sorry.": "Virhe kommenttia tallentaessa. Anteeksi.", + "Error saving paste. Sorry.": "Virhe pastea tallentaessa. Anteeksi.", + "Invalid paste ID.": "Virheellinen paste ID.", + "Paste is not of burn-after-reading type.": "Paste ei ole polta-lukemisen-jälkeen-tyyppiä", + "Wrong deletion token. Paste was not deleted.": "Virheellinen poistotunniste. Pastea ei poistettu.", + "Paste was properly deleted.": "Paste poistettiin kunnolla.", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScriptiä tarvitaan jotta %s toimisi. Anteeksi haitasta.", + "%s requires a modern browser to work.": "%s tarvitsee modernia selainta toimiakseen.", + "New": "Uusi", + "Send": "Lähetä", + "Clone": "Kloonaa", + "Raw text": "Raaka teksti", + "Expires": "Vanhenee", + "Burn after reading": "Polta lukemisen jälkeen", "Open discussion": "Open discussion", - "Password (recommended)": "Password (recommended)", - "Discussion": "Discussion", + "Password (recommended)": "Salasana (suositeltu)", + "Discussion": "Keskustelu", "Toggle navigation": "Toggle navigation", "%d seconds": [ - "%d second (singular)", - "%d seconds (1st plural)", - "%d seconds (2nd plural)", - "%d seconds (3rd plural)" + "%d sekunti", + "%d sekuntia", + "%d sekuntia", + "%d sekuntia" ], "%d minutes": [ - "%d minute (singular)", - "%d minutes (1st plural)", - "%d minutes (2nd plural)", - "%d minutes (3rd plural)" + "%d minuutti", + "%d minuuttia", + "%d minuuttia", + "%d minuuttia" ], "%d hours": [ - "%d hour (singular)", - "%d hours (1st plural)", - "%d hours (2nd plural)", - "%d hours (3rd plural)" + "%d tunti", + "%d tuntia", + "%d tuntia", + "%d tuntia" ], "%d days": [ - "%d day (singular)", - "%d days (1st plural)", - "%d days (2nd plural)", - "%d days (3rd plural)" + "%d päivä", + "%d päivää", + "%d päivää", + "%d päivää" ], "%d weeks": [ - "%d week (singular)", - "%d weeks (1st plural)", - "%d weeks (2nd plural)", - "%d weeks (3rd plural)" + "%d viikko", + "%d viikkoa", + "%d viikkoa", + "%d viikkoa" ], "%d months": [ - "%d month (singular)", - "%d months (1st plural)", - "%d months (2nd plural)", - "%d months (3rd plural)" + "%d kuukausi", + "%d kuukautta", + "%d kuukautta", + "%d kuukautta" ], "%d years": [ - "%d year (singular)", - "%d years (1st plural)", - "%d years (2nd plural)", - "%d years (3rd plural)" + "%d vuosi", + "%d vuotta", + "%d vuotta", + "%d vuotta" ], - "Never": "Never", + "Never": "Ei koskaan", "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)" + "Tämä dokumentti vanhenee %d sekuntissa.", + "Tämä dokumentti vanhenee %d sekunnissa.", + "Tämä dokumentti vanhenee %d sekunnissa.", + "Tämä dokumentti vanhenee %d sekunnissa." ], "This document will expire in %d minutes.": [ - "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)" + "Tämä dokumentti vanhenee %d minuutissa.", + "Tämä dokumentti vanhenee %d minuutissa.", + "Tämä dokumentti vanhenee %d minuutissa.", + "Tämä dokumentti vanhenee %d minuutissa." ], "This document will expire in %d hours.": [ - "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)" + "Tämä dokumentti vanhenee %d tunnissa.", + "Tämä dokumentti vanhenee %d tunnissa.", + "Tämä dokumentti vanhenee %d tunnissa.", + "Tämä dokumentti vanhenee %d tunnissa." ], "This document will expire in %d days.": [ - "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)" + "Tämä dokumentti vanhenee %d päivässä.", + "Tämä dokumentti vanhenee %d päivässä.", + "Tämä dokumentti vanhenee %d päivässä.", + "Tämä dokumentti vanhenee %d päivässä." ], "This document will expire in %d months.": [ - "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)" + "Tämä dokumentti vanhenee %d kuukaudessa.", + "Tämä dokumentti vanhenee %d kuukaudessa.", + "Tämä dokumentti vanhenee %d kuukaudessa.", + "Tämä dokumentti vanhenee %d kuukaudessa." ], - "Please enter the password for this paste:": "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.", + "Please enter the password for this paste:": "Syötä salasana tälle pastelle:", + "Could not decrypt data (Wrong key?)": "Dataa ei voitu purkaa (Väärä avain?)", + "Could not delete the paste, it was not stored in burn after reading mode.": "Pastea ei voitu poistaa, sitä ei säilytetty \"Polta lukemisen jälkeen\" -tilassa.", + "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "VAIN SINUN SILMILLESI. Älä sulje tätä ikkunaa, tätä viestiä ei voida näyttää uudelleen.", + "Could not decrypt comment; Wrong key?": "Kommenttia ei voitu purkaa; väärä avain?", + "Reply": "Vastaa", + "Anonymous": "Anonyymi", + "Avatar generated from IP address": "Avatar generoitu IP-osoitteesta", + "Add comment": "Lisää kommentti", + "Optional nickname…": "Valinnainen nimimerkki…", + "Post comment": "Lähetä kommentti", + "Sending comment…": "Lähetetään kommenttia…", + "Comment posted.": "Kommentti lähetetty.", "Could not refresh display: %s": "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", + "Could not post comment: %s": "Kommenttia ei voitu lähettää: %s", + "Sending paste…": "Lähetetään pastea…", + "Your paste is %s (Hit [Ctrl]+[c] to copy)": "Pastesi on %s (Paina [Ctrl]+[c] kopioidaksesi)", + "Delete data": "Poista data", + "Could not create paste: %s": "Pastea ei voitu luoda: %s", "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "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", @@ -139,33 +139,33 @@ "EiB": "EiB", "ZiB": "ZiB", "YiB": "YiB", - "Format": "Format", - "Plain Text": "Plain Text", - "Source Code": "Source Code", + "Format": "Formaatti", + "Plain Text": "Perusteksti", + "Source Code": "Lähdekoodi", "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", + "Download attachment": "Lataa liite", + "Cloned: '%s'": "Kloonattu: '%s'", + "The cloned file '%s' was attached to this paste.": "Kloonattu tiedosto '%s' liitettiin tähän pasteen", + "Attach a file": "Liitä tiedosto", "alternatively drag & drop a file or paste an image from the clipboard": "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", + "Options": "Asetukset", + "Shorten URL": "Lyhennä URL", + "Editor": "Muokkaaja", + "Preview": "Esikatselu", "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.", - "Decrypt": "Decrypt", - "Enter password": "Enter password", - "Loading…": "Loading…", + "Decrypt": "Pura", + "Enter password": "Syötä salasana", + "Loading…": "Ladataan…", "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-koodi", "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.", @@ -177,13 +177,13 @@ "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:", + "Link:": "Linkki:", "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": "Sulje", "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" + "Save paste": "Tallenna paste" } From bb3af09c6c86d0d70d8e9a76d1f50fd9f07e249a Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 31 Jan 2022 13:37:59 +0100 Subject: [PATCH 219/478] New translations en.json (Finnish) --- i18n/fi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/fi.json b/i18n/fi.json index 0dd27d5..7b6294e 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -180,7 +180,7 @@ "Link:": "Linkki:", "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", + "Convert To UTC": "Muuta UTC:ksi", "Close": "Sulje", "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.", From 3b472fdd4129abe8398b85bfb9f02485d1e04178 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 31 Jan 2022 14:40:40 +0100 Subject: [PATCH 220/478] New translations en.json (Finnish) --- i18n/fi.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/i18n/fi.json b/i18n/fi.json index 7b6294e..a6c720e 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -6,7 +6,7 @@ "en": "fi", "Paste does not exist, has expired or has been deleted.": "Pastea ei ole olemassa, se on vanhentunut, tai se on poistettu.", "%s requires php %s or above to work. Sorry.": "%s tarvitsee php %s-versiota tai uudempaa toimiakseen. Anteeksi.", - "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", + "%s requires configuration section [%s] to be present in configuration file.": "%s vaatii konfiguraatio-osion [%s] olevan läsnä konfiguraatiotiedostossa.", "Please wait %d seconds between each post.": [ "Please wait %d second between each post. (singular)", "Please wait %d seconds between each post. (1st plural)", @@ -151,7 +151,7 @@ "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.", + "Invalid attachment.": "Virheellinen liite.", "Options": "Asetukset", "Shorten URL": "Lyhennä URL", "Editor": "Muokkaaja", @@ -168,18 +168,18 @@ "QR code": "QR-koodi", "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 may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Selaimesi ehkä tarvitsee HTTPS-yhteyden tukeakseen WebCrypto API:a. Yritä vaihtamista HTTPS:ään.", "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:", + "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Dataa ei voitu purkaa. Syötitkö väärän salasanan? Yritä uudelleen ylhäällä olevalla painikkeella.", + "Retry": "Yritä uudelleen", + "Showing raw text…": "Näytetään raaka reksti…", + "Notice:": "Huomautus:", "This link will expire after %s.": "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:": "Linkki:", "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", + "Use Current Timezone": "Käytä nykyistä aikavyöhykettä", "Convert To UTC": "Muuta UTC:ksi", "Close": "Sulje", "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", From f3b14225ba82d74a4a6e06ad358f87e91661f1fc Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 31 Jan 2022 15:52:29 +0100 Subject: [PATCH 221/478] New translations en.json (Finnish) --- i18n/fi.json | 64 ++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/i18n/fi.json b/i18n/fi.json index a6c720e..aea2b63 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -2,16 +2,16 @@ "PrivateBin": "PrivateBin", "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s on minimalistinen, avoimen lähdekoodin online pastebin jossa palvelimella ei ole tietoa syötetystä datasta. Data salataan/puretaan %sselaimessa%s käyttäen 256-bittistä AES:ää.", "More information on the project page.": "Enemmän tietoa projektisivulla.", - "Because ignorance is bliss": "Because ignorance is bliss", + "Because ignorance is bliss": "Koska tieto lisää tuskaa", "en": "fi", "Paste does not exist, has expired or has been deleted.": "Pastea ei ole olemassa, se on vanhentunut, tai se on poistettu.", "%s requires php %s or above to work. Sorry.": "%s tarvitsee php %s-versiota tai uudempaa toimiakseen. Anteeksi.", "%s requires configuration section [%s] to be present in configuration file.": "%s vaatii konfiguraatio-osion [%s] olevan läsnä konfiguraatiotiedostossa.", "Please wait %d seconds between each post.": [ - "Please wait %d second between each post. (singular)", - "Please wait %d seconds between each post. (1st plural)", - "Please wait %d seconds between each post. (2nd plural)", - "Please wait %d seconds between each post. (3rd plural)" + "Odotathan %d sekuntin jokaisen lähetyksen välillä.", + "Odotathan %d sekuntia jokaisen lähetyksen välillä.", + "Odotathan %d sekuntia jokaisen lähetyksen välillä.", + "Odotathan %d sekuntia jokaisen lähetyksen välillä." ], "Paste is limited to %s of encrypted data.": "Paste on rajoitettu kokoon %s salattua dataa.", "Invalid data.": "Virheellinen data.", @@ -30,10 +30,10 @@ "Raw text": "Raaka teksti", "Expires": "Vanhenee", "Burn after reading": "Polta lukemisen jälkeen", - "Open discussion": "Open discussion", + "Open discussion": "Avaa keskustelu", "Password (recommended)": "Salasana (suositeltu)", "Discussion": "Keskustelu", - "Toggle navigation": "Toggle navigation", + "Toggle navigation": "Navigointi päällä/pois", "%d seconds": [ "%d sekunti", "%d sekuntia", @@ -77,7 +77,7 @@ "%d vuotta" ], "Never": "Ei koskaan", - "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.": "Huomaa: Tämä on testipalvelu: Data voidaan poistaa milloin tahansa. Kissanpennut kuolevat jos väärinkäytät tätä palvelua.", "This document will expire in %d seconds.": [ "Tämä dokumentti vanhenee %d sekuntissa.", "Tämä dokumentti vanhenee %d sekunnissa.", @@ -121,15 +121,15 @@ "Post comment": "Lähetä kommentti", "Sending comment…": "Lähetetään kommenttia…", "Comment posted.": "Kommentti lähetetty.", - "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 refresh display: %s": "Näyttöä ei voitu päivittää: %s", + "unknown status": "tuntematon status", + "server error or not responding": "palvelinvirhe tai palvelin ei vastaa", "Could not post comment: %s": "Kommenttia ei voitu lähettää: %s", "Sending paste…": "Lähetetään pastea…", "Your paste is %s (Hit [Ctrl]+[c] to copy)": "Pastesi on %s (Paina [Ctrl]+[c] kopioidaksesi)", "Delete data": "Poista data", "Could not create paste: %s": "Pastea ei voitu luoda: %s", - "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "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?)": "Pastea ei voitu purkaa: Purkausavain puuttuu URL:stä (Käytitkö uudelleenohjaajaa tai URL-lyhentäjää joka poistaa osan URL:stä?)", "B": "B", "KiB": "KiB", "MiB": "MiB", @@ -147,43 +147,43 @@ "Cloned: '%s'": "Kloonattu: '%s'", "The cloned file '%s' was attached to this paste.": "Kloonattu tiedosto '%s' liitettiin tähän pasteen", "Attach a file": "Liitä tiedosto", - "alternatively drag & drop a file or paste an image from the clipboard": "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.", + "alternatively drag & drop a file or paste an image from the clipboard": "vaihtoehtoisesti vedä & pudota tiedosto tai liitä kuva leikepöydältä", + "File too large, to display a preview. Please download the attachment.": "Tiedosto on liian iso esikatselun näyttämiseksi. Lataathan liitteen.", + "Remove attachment": "Poista liite", + "Your browser does not support uploading encrypted files. Please use a newer browser.": "Selaimesi ei tue salattujen tiedostojen lataamista. Käytäthän uudempaa selainta.", "Invalid attachment.": "Virheellinen liite.", "Options": "Asetukset", "Shorten URL": "Lyhennä URL", "Editor": "Muokkaaja", "Preview": "Esikatselu", - "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s 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.": "%s vaatii PATH:in loppuvan \"%s\"-merkkiin. Päivitäthän PATH:in index.php:ssäsi.", "Decrypt": "Pura", "Enter password": "Syötä salasana", "Loading…": "Ladataan…", - "Decrypting paste…": "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", + "Decrypting paste…": "Puretaan pastea…", + "Preparing new paste…": "Valmistellaan uutta pastea", + "In case this message never disappears please have a look at this FAQ for information to troubleshoot.": "Jos tämä viesti ei katoa koskaan, katsothan tämän FAQ:n ongelmanratkaisutiedon löytämiseksi.", + "+++ no paste text +++": "+++ ei paste-tekstiä +++", + "Could not get paste data: %s": "Paste-tietoja ei löydetty: %s", "QR code": "QR-koodi", - "This website is using an insecure HTTP connection! Please use it only for testing.": "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.", + "This website is using an insecure HTTP connection! Please use it only for testing.": "Tämä sivusto käyttää epäturvallista HTTP-yhteyttä! Käytäthän sitä vain testaukseen.", + "For more information see this FAQ entry.": "Lisätietoja varten lue tämä FAQ-kohta.", "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Selaimesi ehkä tarvitsee HTTPS-yhteyden tukeakseen WebCrypto API:a. Yritä vaihtamista HTTPS:ään.", - "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", + "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Selaimesi ei tue WebAssemblyä jota käytetään zlib-pakkaamiseen. Voit luoda pakkaamattomia dokumentteja, mutta et voi lukea pakattuja dokumentteja.", + "waiting on user to provide a password": "odotetaan käyttäjän antavan salasanan", "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Dataa ei voitu purkaa. Syötitkö väärän salasanan? Yritä uudelleen ylhäällä olevalla painikkeella.", "Retry": "Yritä uudelleen", "Showing raw text…": "Näytetään raaka reksti…", "Notice:": "Huomautus:", - "This link will expire after %s.": "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.", + "This link will expire after %s.": "Tämä linkki vanhenee ajan %s jälkeen.", + "This link can only be accessed once, do not use back or refresh button in your browser.": "Tätä linkkiä voidaan käyttää vain kerran, älä käytä taaksepäin- tai päivityspainiketta selaimessasi.", "Link:": "Linkki:", - "Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?", + "Recipient may become aware of your timezone, convert time to UTC?": "Vastaanottaja saattaa tulla tietoiseksi aikavyöhykkeestäsi, muutetaanko aika UTC:ksi?", "Use Current Timezone": "Käytä nykyistä aikavyöhykettä", "Convert To UTC": "Muuta UTC:ksi", "Close": "Sulje", - "Encrypted note on PrivateBin": "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.", + "Encrypted note on PrivateBin": "Salattu viesti PrivateBinissä", + "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Käy tässä linkissä nähdäksesi viestin. URL:n antaminen kenellekään antaa heidänkin päästä katsomeen viestiä. ", + "URL shortener may expose your decrypt key in URL.": "URL-lyhentäjä voi paljastaa purkuavaimesi URL:ssä.", "Save paste": "Tallenna paste" } From 401cd32d07ce41f2ea71d7fe19298c864896239f Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Wed, 9 Feb 2022 22:30:53 -0800 Subject: [PATCH 222/478] add jb (lojban) into supported languages list --- js/privatebin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/privatebin.js b/js/privatebin.js index 6218700..860a2bd 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -601,7 +601,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @prop {string[]} * @readonly */ - const supportedLanguages = ['bg', 'ca', 'cs', 'de', 'es', 'et', 'fr', 'he', 'hu', 'id', 'it', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh']; + const supportedLanguages = ['bg', 'ca', 'cs', 'de', 'es', 'et', 'fr', 'he', 'hu', 'id', 'it', 'jb', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh']; /** * built in language From cadfb0919339a1dc9de6210e15a256217d1d88f3 Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Wed, 9 Feb 2022 22:39:09 -0800 Subject: [PATCH 223/478] copy en.json into jb.json --- i18n/jb.json | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 i18n/jb.json diff --git a/i18n/jb.json b/i18n/jb.json new file mode 100644 index 0000000..a96bab5 --- /dev/null +++ b/i18n/jb.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 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.", + "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", + "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", + "Please wait %d seconds between each post.": [ + "Please wait %d second between each post. (singular)", + "Please wait %d seconds between each post. (1st plural)", + "Please wait %d seconds between each post. (2nd plural)", + "Please wait %d seconds between each post. (3rd plural)" + ], + "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", + "Invalid data.": "Invalid data.", + "You are unlucky. Try again.": "You are unlucky. Try again.", + "Error saving comment. Sorry.": "Error saving comment. Sorry.", + "Error saving paste. Sorry.": "Error saving paste. Sorry.", + "Invalid paste ID.": "Invalid paste ID.", + "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", + "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", + "Paste was properly deleted.": "Paste was properly deleted.", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", + "%s requires a modern browser to work.": "%s requires a modern browser to work.", + "New": "New", + "Send": "Send", + "Clone": "Clone", + "Raw text": "Raw text", + "Expires": "Expires", + "Burn after reading": "Burn after reading", + "Open discussion": "Open discussion", + "Password (recommended)": "Password (recommended)", + "Discussion": "Discussion", + "Toggle navigation": "Toggle navigation", + "%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" +} From 79abba5124f8e52a46ddd9cfc049a7419cb6948c Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Wed, 9 Feb 2022 22:44:22 -0800 Subject: [PATCH 224/478] Update jb.json baby steps --- i18n/jb.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/jb.json b/i18n/jb.json index a96bab5..23c6c2d 100644 --- a/i18n/jb.json +++ b/i18n/jb.json @@ -1,9 +1,9 @@ { - "PrivateBin": "PrivateBin", + "PrivateBin": "sivnibot", "%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", + "en": "jb", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", From 08d8922aaded9a6d039d4631f9997b03e765e9cf Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Wed, 9 Feb 2022 22:50:19 -0800 Subject: [PATCH 225/478] change name to the more sensical "patxu" --- i18n/jb.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/jb.json b/i18n/jb.json index 23c6c2d..42069d8 100644 --- a/i18n/jb.json +++ b/i18n/jb.json @@ -1,5 +1,5 @@ { - "PrivateBin": "sivnibot", + "PrivateBin": "sivnipax", "%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", From 902e7cf480f5a3123b2875a35999c8a24510b77f Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Wed, 9 Feb 2022 23:27:54 -0800 Subject: [PATCH 226/478] add lojban --- i18n/languages.json | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/languages.json b/i18n/languages.json index a2aff4a..671b3f7 100644 --- a/i18n/languages.json +++ b/i18n/languages.json @@ -89,6 +89,7 @@ "ku": ["Kurdî", "Kurdish"], "kj": ["Kuanyama", "Kwanyama"], "la": ["lingua latina", "Latin"], + "jb": ["jbobau", "Lojban"], "lb": ["Lëtzebuergesch", "Luxembourgish"], "lg": ["Luganda", "Ganda"], "li": ["Limburgs", "Limburgish"], From 77dd0b027f154b36a81c20dd0f62d010fd5bc4fe Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Thu, 10 Feb 2022 00:30:14 -0800 Subject: [PATCH 227/478] roughly 2% of random lojban stuff --- i18n/jb.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/i18n/jb.json b/i18n/jb.json index 42069d8..6e8e08f 100644 --- a/i18n/jb.json +++ b/i18n/jb.json @@ -2,7 +2,7 @@ "PrivateBin": "sivnipax", "%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", + "Because ignorance is bliss": ".i ki'u le ka na djuno cu ka saxfri", "en": "jb", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", @@ -14,7 +14,7 @@ "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.", + "Invalid data.": ".i le selru'e cu na drani", "You are unlucky. Try again.": "You are unlucky. Try again.", "Error saving comment. Sorry.": "Error saving comment. Sorry.", "Error saving paste. Sorry.": "Error saving paste. Sorry.", @@ -24,10 +24,10 @@ "Paste was properly deleted.": "Paste was properly deleted.", "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", "%s requires a modern browser to work.": "%s requires a modern browser to work.", - "New": "New", - "Send": "Send", - "Clone": "Clone", - "Raw text": "Raw text", + "New": "cnino", + "Send": "benji", + "Clone": "fukpi", + "Raw text": "vlapoi nalselrucyzu'e", "Expires": "Expires", "Burn after reading": "Burn after reading", "Open discussion": "Open discussion", From 1d6bcb1f5746d6d669a16496db293735f9ffddca Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Thu, 10 Feb 2022 09:48:16 -0800 Subject: [PATCH 228/478] Update jb.json --- i18n/jb.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/jb.json b/i18n/jb.json index 6e8e08f..ff392b8 100644 --- a/i18n/jb.json +++ b/i18n/jb.json @@ -1,6 +1,6 @@ { "PrivateBin": "sivnipax", - "%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.", + "%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.": "la %s lo la fukpipax kibro ku .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i 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": ".i ki'u le ka na djuno cu ka saxfri", "en": "jb", From 15374f99aeef48ec67c57fad65ca65c5817ed7c7 Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Thu, 10 Feb 2022 10:33:11 -0800 Subject: [PATCH 229/478] name change, little better --- i18n/jb.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/jb.json b/i18n/jb.json index ff392b8..cf04d97 100644 --- a/i18n/jb.json +++ b/i18n/jb.json @@ -1,6 +1,6 @@ { - "PrivateBin": "sivnipax", - "%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.": "la %s lo la fukpipax kibro ku .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", + "PrivateBin": "sivbaktu", + "%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.": "la %s basti lo sorcu lo'e se setca kibro .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i 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": ".i ki'u le ka na djuno cu ka saxfri", "en": "jb", From 34923addd7ef01959ba92c39c937617c5703cdf5 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 10 Feb 2022 20:16:16 +0100 Subject: [PATCH 230/478] New translations en.json (Lojban) --- i18n/jbo.json | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 i18n/jbo.json diff --git a/i18n/jbo.json b/i18n/jbo.json new file mode 100644 index 0000000..a96bab5 --- /dev/null +++ b/i18n/jbo.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 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.", + "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", + "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", + "Please wait %d seconds between each post.": [ + "Please wait %d second between each post. (singular)", + "Please wait %d seconds between each post. (1st plural)", + "Please wait %d seconds between each post. (2nd plural)", + "Please wait %d seconds between each post. (3rd plural)" + ], + "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", + "Invalid data.": "Invalid data.", + "You are unlucky. Try again.": "You are unlucky. Try again.", + "Error saving comment. Sorry.": "Error saving comment. Sorry.", + "Error saving paste. Sorry.": "Error saving paste. Sorry.", + "Invalid paste ID.": "Invalid paste ID.", + "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", + "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", + "Paste was properly deleted.": "Paste was properly deleted.", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", + "%s requires a modern browser to work.": "%s requires a modern browser to work.", + "New": "New", + "Send": "Send", + "Clone": "Clone", + "Raw text": "Raw text", + "Expires": "Expires", + "Burn after reading": "Burn after reading", + "Open discussion": "Open discussion", + "Password (recommended)": "Password (recommended)", + "Discussion": "Discussion", + "Toggle navigation": "Toggle navigation", + "%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" +} From 5b5f1e3aa55f3125ccc6b283e3c61e1a2d3b4063 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 10 Feb 2022 21:23:20 +0100 Subject: [PATCH 231/478] New translations en.json (Lojban) --- i18n/jbo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/jbo.json b/i18n/jbo.json index a96bab5..069eec2 100644 --- a/i18n/jbo.json +++ b/i18n/jbo.json @@ -1,9 +1,9 @@ { - "PrivateBin": "PrivateBin", + "PrivateBin": "sivlolnitvanku'a", "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%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", + "en": "jb", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", From b21d5afa2d3a59dbf72b9be818c8c5984671d579 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Thu, 10 Feb 2022 22:21:16 +0100 Subject: [PATCH 232/478] New translations en.json (Lojban) --- i18n/jbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/jbo.json b/i18n/jbo.json index 069eec2..6f63c31 100644 --- a/i18n/jbo.json +++ b/i18n/jbo.json @@ -1,6 +1,6 @@ { "PrivateBin": "sivlolnitvanku'a", - "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%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.": ".i la %s mupli lo sorcu lo'e se setca kibro .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i ba'e %sle brauzero%s mipri le do datni ku fi la'oi AES poi bitni li 256", "More information on the project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", "en": "jb", From 86da334e8f6fdfdb8b04eff49ed3b587a039e058 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Fri, 11 Feb 2022 00:18:31 +0100 Subject: [PATCH 233/478] New translations en.json (Lojban) --- i18n/jbo.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/i18n/jbo.json b/i18n/jbo.json index 6f63c31..405857f 100644 --- a/i18n/jbo.json +++ b/i18n/jbo.json @@ -1,8 +1,8 @@ { "PrivateBin": "sivlolnitvanku'a", - "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": ".i la %s mupli lo sorcu lo'e se setca kibro .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i ba'e %sle brauzero%s mipri le do datni ku fi la'oi AES poi bitni li 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.": ".i la %s mupli lo sorcu lo'e se setca kibro .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i ba'e %sle brauzero%s ku mipri le do datni ku fi la'oi AES poi bitni li 256", "More information on the project page.": "More information on the project page.", - "Because ignorance is bliss": "Because ignorance is bliss", + "Because ignorance is bliss": ".i ki'u le ka na djuno cu ka saxfri", "en": "jb", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", @@ -14,7 +14,7 @@ "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.", + "Invalid data.": ".i le selru'e cu na drani", "You are unlucky. Try again.": "You are unlucky. Try again.", "Error saving comment. Sorry.": "Error saving comment. Sorry.", "Error saving paste. Sorry.": "Error saving paste. Sorry.", @@ -24,10 +24,10 @@ "Paste was properly deleted.": "Paste was properly deleted.", "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", "%s requires a modern browser to work.": "%s requires a modern browser to work.", - "New": "New", - "Send": "Send", - "Clone": "Clone", - "Raw text": "Raw text", + "New": "cnino", + "Send": "benji", + "Clone": "fukpi", + "Raw text": "vlapoi nalselrucyzu'e", "Expires": "Expires", "Burn after reading": "Burn after reading", "Open discussion": "Open discussion", From 49d0b35dc85f0a8a4ce17768f4b7348ec60cb0ae Mon Sep 17 00:00:00 2001 From: AckKid <81313252+AckKid@users.noreply.github.com> Date: Thu, 10 Feb 2022 18:53:39 -0500 Subject: [PATCH 234/478] Create Procfile --- Procfile | 1 + 1 file changed, 1 insertion(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..a88c976 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: vendor/bin/heroku-php-apache2 From 8b0ab8ee492f591dab83617ff0e4203a65b56bbb Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Fri, 11 Feb 2022 01:30:42 +0100 Subject: [PATCH 235/478] New translations en.json (Lojban) --- i18n/jbo.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/i18n/jbo.json b/i18n/jbo.json index 405857f..068da1a 100644 --- a/i18n/jbo.json +++ b/i18n/jbo.json @@ -24,15 +24,15 @@ "Paste was properly deleted.": "Paste was properly deleted.", "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", "%s requires a modern browser to work.": "%s requires a modern browser to work.", - "New": "cnino", - "Send": "benji", - "Clone": "fukpi", - "Raw text": "vlapoi nalselrucyzu'e", - "Expires": "Expires", - "Burn after reading": "Burn after reading", - "Open discussion": "Open discussion", - "Password (recommended)": "Password (recommended)", - "Discussion": "Discussion", + "New": ".i cnino", + "Send": ".i benji", + "Clone": ".i fukpi", + "Raw text": ".i vlapoi nalselrucyzu'e", + "Expires": ".i vimcu", + "Burn after reading": ".i vimcu ba la tcidu", + "Open discussion": ".i lo zbasu cu casnu", + "Password (recommended)": ".i japyvla (nelti'i)", + "Discussion": ".i casnu", "Toggle navigation": "Toggle navigation", "%d seconds": [ "%d second (singular)", @@ -177,7 +177,7 @@ "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:", + "Link:": "urli:", "Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?", "Use Current Timezone": "Use Current Timezone", "Convert To UTC": "Convert To UTC", @@ -185,5 +185,5 @@ "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" + "Save paste": ".i rejgau fukpi" } From 0c0be618e01a2db48f97fc19d152fad68510c286 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Fri, 11 Feb 2022 05:48:20 +0100 Subject: [PATCH 236/478] New translations en.json (Lojban) --- i18n/jbo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/jbo.json b/i18n/jbo.json index 068da1a..8a3f73c 100644 --- a/i18n/jbo.json +++ b/i18n/jbo.json @@ -181,8 +181,8 @@ "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", + "Close": ".i ganlo", + "Encrypted note on PrivateBin": ".i lo lo notci ku mifra cu zvati sivlolnitvanku'a", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", "Save paste": ".i rejgau fukpi" From 6da1fa04c3e40043028f8fbfe379e88d7cf046d8 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Fri, 11 Feb 2022 06:47:01 +0100 Subject: [PATCH 237/478] New translations en.json (Lojban) --- i18n/jbo.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/i18n/jbo.json b/i18n/jbo.json index 8a3f73c..eb7d020 100644 --- a/i18n/jbo.json +++ b/i18n/jbo.json @@ -24,15 +24,15 @@ "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": ".i cnino", - "Send": ".i benji", - "Clone": ".i fukpi", - "Raw text": ".i vlapoi nalselrucyzu'e", - "Expires": ".i vimcu", - "Burn after reading": ".i vimcu ba la tcidu", - "Open discussion": ".i lo zbasu cu casnu", - "Password (recommended)": ".i japyvla (nelti'i)", - "Discussion": ".i casnu", + "New": "cnino", + "Send": "benji", + "Clone": "fukpi", + "Raw text": "vlapoi nalselrucyzu'e", + "Expires": "vimcu", + "Burn after reading": "vimcu ba la tcidu", + "Open discussion": "lo zbasu cu casnu", + "Password (recommended)": "japyvla (nelti'i)", + "Discussion": "casnu", "Toggle navigation": "Toggle navigation", "%d seconds": [ "%d second (singular)", @@ -165,14 +165,14 @@ "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": "ky.bu ry termifra", "This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.", "For more information 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": "refcfa", "Showing raw text…": "Showing raw text…", "Notice:": "Notice:", "This link will expire after %s.": "This link will expire after %s.", @@ -180,10 +180,10 @@ "Link:": "urli:", "Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?", "Use Current Timezone": "Use Current Timezone", - "Convert To UTC": "Convert To UTC", - "Close": ".i ganlo", + "Convert To UTC": "galfi lo cabni la utc", + "Close": "ganlo", "Encrypted note on PrivateBin": ".i lo lo notci ku mifra cu zvati sivlolnitvanku'a", "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Save paste": ".i rejgau fukpi" + "Save paste": "rejgau fukpi" } From 832f0005762ea7d256e398de86b814ae7d87579e Mon Sep 17 00:00:00 2001 From: Bjoern Becker Date: Fri, 11 Feb 2022 12:22:16 +0100 Subject: [PATCH 238/478] update jquery --- js/common.js | 2 +- js/jquery-3.4.1.js | 2 -- js/jquery-3.6.0.js | 2 ++ tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 js/jquery-3.4.1.js create mode 100644 js/jquery-3.6.0.js diff --git a/js/common.js b/js/common.js index b23ed22..4acbbfc 100644 --- a/js/common.js +++ b/js/common.js @@ -10,7 +10,7 @@ global.fs = require('fs'); global.WebCrypto = require('@peculiar/webcrypto').Crypto; // application libraries to test -global.$ = global.jQuery = require('./jquery-3.4.1'); +global.$ = global.jQuery = require('./jquery-3.6.0'); global.RawDeflate = require('./rawinflate-0.3').RawDeflate; global.zlib = require('./zlib-1.2.11').zlib; require('./prettify'); diff --git a/js/jquery-3.4.1.js b/js/jquery-3.4.1.js deleted file mode 100644 index a1c07fd..0000000 --- a/js/jquery-3.4.1.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 - + diff --git a/tpl/page.php b/tpl/page.php index 28f37b9..bc9641e 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -20,7 +20,7 @@ if ($SYNTAXHIGHLIGHTING): endif; endif; ?> - + From 8faf0501f4d39039220070d59935b1ecac4d4086 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 12 Feb 2022 16:17:09 +0100 Subject: [PATCH 239/478] improve Lojban support - Crowdin has to use the 3 letter language code, since Lojban has no 2 letter code. Added support for this in the PHP backend and renamed the translation file. - Lojban has no plural cases, updated the plural-formulas accordingly. - Credited the change and documented it. - Updated the SRI hashes. --- CHANGELOG.md | 4 ++-- CREDITS.md | 1 + i18n/{jb.json => jbo.json} | 0 i18n/languages.json | 2 +- js/privatebin.js | 7 ++++--- lib/I18n.php | 3 ++- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 8 files changed, 12 insertions(+), 9 deletions(-) rename i18n/{jb.json => jbo.json} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 428a60a..7c3f804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # PrivateBin version history * **1.4 (not yet released)** - * ADDED: Translation for Estonian + * ADDED: Translations for Estonian and Lojban * ADDED: new HTTP headers improving security (#765) * ADDED: Download button for paste text (#774) * ADDED: Opt-out of federated learning of cohorts (FLoC) (#776) @@ -14,7 +14,7 @@ * CHANGED: Removed configurable `dir` for `traffic` & `purge` limiters (#419) * CHANGED: Server salt, traffic and purge limiter now stored in the storage backend (#419) * **1.3.5 (2021-04-05)** - * ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan + * ADDED: Translations for Hebrew, Lithuanian, Indonesian and Catalan * ADDED: Make the project info configurable (#681) * CHANGED: Upgrading libraries to: DOMpurify 2.2.7, kjua 0.9.0 & random_compat 2.0.18 * CHANGED: Open all links in new window (#630) diff --git a/CREDITS.md b/CREDITS.md index 6c2f647..de0ebe8 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -54,3 +54,4 @@ Sébastien Sauvage - original idea and main developer * whenwesober - Indonesian * retiolus - Catalan * sarnane - Estonian +* foxsouns - Lojban diff --git a/i18n/jb.json b/i18n/jbo.json similarity index 100% rename from i18n/jb.json rename to i18n/jbo.json diff --git a/i18n/languages.json b/i18n/languages.json index 671b3f7..2d7341d 100644 --- a/i18n/languages.json +++ b/i18n/languages.json @@ -89,7 +89,7 @@ "ku": ["Kurdî", "Kurdish"], "kj": ["Kuanyama", "Kwanyama"], "la": ["lingua latina", "Latin"], - "jb": ["jbobau", "Lojban"], + "jbo":["jbobau", "Lojban"], "lb": ["Lëtzebuergesch", "Luxembourgish"], "lg": ["Luganda", "Ganda"], "li": ["Limburgs", "Limburgish"], diff --git a/js/privatebin.js b/js/privatebin.js index 860a2bd..ef030fb 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -601,7 +601,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @prop {string[]} * @readonly */ - const supportedLanguages = ['bg', 'ca', 'cs', 'de', 'es', 'et', 'fr', 'he', 'hu', 'id', 'it', 'jb', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh']; + const supportedLanguages = ['bg', 'ca', 'cs', 'de', 'es', 'et', 'fr', 'he', 'hu', 'id', 'it', 'jbo', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh']; /** * built in language @@ -785,6 +785,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { case 'he': return n === 1 ? 0 : (n === 2 ? 1 : ((n < 0 || n > 10) && (n % 10 === 0) ? 2 : 3)); case 'id': + case 'jbo': return 0; case 'lt': return n % 10 === 1 && n % 100 !== 11 ? 0 : ((n % 10 >= 2 && n % 100 < 10 || n % 100 >= 20) ? 1 : 2); @@ -5404,8 +5405,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { node.setAttribute('target', '_blank'); } // set non-HTML/MathML links to xlink:show=new - if (!node.hasAttribute('target') - && (node.hasAttribute('xlink:href') + if (!node.hasAttribute('target') + && (node.hasAttribute('xlink:href') || node.hasAttribute('href'))) { node.setAttribute('xlink:show', 'new'); } diff --git a/lib/I18n.php b/lib/I18n.php index 50bf0cc..bc8b765 100644 --- a/lib/I18n.php +++ b/lib/I18n.php @@ -195,7 +195,7 @@ class I18n if (count(self::$_availableLanguages) == 0) { $i18n = dir(self::_getPath()); while (false !== ($file = $i18n->read())) { - if (preg_match('/^([a-z]{2}).json$/', $file, $match) === 1) { + if (preg_match('/^([a-z]{2,3}).json$/', $file, $match) === 1) { self::$_availableLanguages[] = $match[1]; } } @@ -324,6 +324,7 @@ class I18n case 'he': return $n === 1 ? 0 : ($n === 2 ? 1 : (($n < 0 || $n > 10) && ($n % 10 === 0) ? 2 : 3)); case 'id': + case 'jbo': return 0; case 'lt': return $n % 10 === 1 && $n % 100 !== 11 ? 0 : (($n % 10 >= 2 && $n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 1e4eae0..ee89ea9 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -72,7 +72,7 @@ endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index 28f37b9..98e3209 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ endif; ?> - + From 186dd82653f65b67d005036972465717e9a6fb5e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 12 Feb 2022 16:41:09 +0100 Subject: [PATCH 240/478] Apply StyleCI fix that class name we used was not quite correct, but PHP tolerated the typo --- tst/Bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index 70aafdd..5b6012f 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -304,7 +304,7 @@ class StorageObjectStub extends StorageObject $this->_generation = $generation; $this->_info = $info; $this->_connection = $connection; - $timeCreated = new Datetime(); + $timeCreated = new DateTime(); $this->_info['metadata']['timeCreated'] = $timeCreated->format('Y-m-d\TH:i:s.u\Z'); } From f5f07b928819076e04f3385f60c8000eaf861473 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sat, 12 Feb 2022 18:24:14 +0100 Subject: [PATCH 241/478] New translations en.json (Lojban) --- i18n/jbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/jbo.json b/i18n/jbo.json index eb7d020..10925d0 100644 --- a/i18n/jbo.json +++ b/i18n/jbo.json @@ -3,7 +3,7 @@ "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": ".i la %s mupli lo sorcu lo'e se setca kibro .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i ba'e %sle brauzero%s ku mipri le do datni ku fi la'oi AES poi bitni li 256", "More information on the project page.": "More information on the project page.", "Because ignorance is bliss": ".i ki'u le ka na djuno cu ka saxfri", - "en": "jb", + "en": "jbo", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", From a200f8875ca0ad0e5f1405aed7ca7a5e2d534138 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 15 Feb 2022 19:02:44 +0100 Subject: [PATCH 242/478] php warning in templates, fixes #875 --- lib/Configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Configuration.php b/lib/Configuration.php index d8ef346..c56ec8e 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -44,7 +44,7 @@ class Configuration 'fileupload' => false, 'burnafterreadingselected' => false, 'defaultformatter' => 'plaintext', - 'syntaxhighlightingtheme' => null, + 'syntaxhighlightingtheme' => '', 'sizelimit' => 10485760, 'template' => 'bootstrap', 'info' => 'More information on the project page.', From 2d7f5e9a9f862d2a3c623478000768bc29714ca1 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Thu, 17 Feb 2022 20:44:49 +0100 Subject: [PATCH 243/478] allow for Lojban (jbo) to be the "any" language pick The available language list is generated by reading the i18n directory descriptor one entry at a time, so if the jbo.json happens to be the first file written to the directory it will be on top of the list and get picked. This is an edge case, most users browsers won't be set to that, but we need to cover this allowed and valid use case in the language detection. --- tst/I18nTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/I18nTest.php b/tst/I18nTest.php index 7d549ba..a70f6b3 100644 --- a/tst/I18nTest.php +++ b/tst/I18nTest.php @@ -145,7 +145,7 @@ class I18nTest extends PHPUnit_Framework_TestCase { $_SERVER['HTTP_ACCEPT_LANGUAGE'] = '*'; I18n::loadTranslations(); - $this->assertTrue(strlen(I18n::_('en')) == 2, 'browser language any'); + $this->assertTrue(strlen(I18n::_('en')) >= 2, 'browser language any'); } public function testVariableInjection() From 6cc47e6073025003a7bcae17e576f5b6ede137f1 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Fri, 18 Feb 2022 00:42:37 +0100 Subject: [PATCH 244/478] New translations en.json (Catalan) --- i18n/ca.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/ca.json b/i18n/ca.json index 50db9fa..67780bf 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -76,7 +76,7 @@ "%d years (2nd plural)", "%d years (3rd plural)" ], - "Never": "Never", + "Never": "Mai", "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.", "This document will expire in %d seconds.": [ "This document will expire in %d second. (singular)", @@ -108,8 +108,8 @@ "This document will expire in %d months. (2nd plural)", "This document will expire in %d months. (3rd plural)" ], - "Please enter the password for this paste:": "Please enter the password for this paste:", - "Could not decrypt data (Wrong key?)": "Could not decrypt data (Wrong key?)", + "Please enter the password for this paste:": "Si us plau, introdueix la contrasenya per aquest paste:", + "Could not decrypt data (Wrong key?)": "No s'han pogut desxifrar les dades (Clau incorrecte?)", "Could not delete the paste, it was not stored in burn after reading mode.": "Could not delete the paste, it was not stored in burn after reading mode.", "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.", "Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?", From 76bc8590a675cb8bbd2611195ecb0b89603753c8 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Fri, 18 Feb 2022 01:37:48 +0100 Subject: [PATCH 245/478] New translations en.json (Catalan) --- i18n/ca.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/i18n/ca.json b/i18n/ca.json index 67780bf..02b48d7 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -113,21 +113,21 @@ "Could not delete the paste, it was not stored in burn after reading mode.": "Could not delete the paste, it was not stored in burn after reading mode.", "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.", "Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?", - "Reply": "Reply", - "Anonymous": "Anonymous", - "Avatar generated from IP address": "Avatar generated from IP address", - "Add comment": "Add comment", - "Optional nickname…": "Optional nickname…", - "Post comment": "Post comment", - "Sending comment…": "Sending comment…", - "Comment posted.": "Comment posted.", + "Reply": "Respondre", + "Anonymous": "Anònim", + "Avatar generated from IP address": "Avatar generat a partir de l'adreça IP", + "Add comment": "Afegir comentari", + "Optional nickname…": "Pseudònim opcional…", + "Post comment": "Publicar comentari", + "Sending comment…": "Enviant comentari…", + "Comment posted.": "Comentari publicat.", "Could not refresh display: %s": "Could not refresh display: %s", - "unknown status": "unknown status", + "unknown status": "estat desconegut", "server error or not responding": "server error or not responding", "Could not post comment: %s": "Could not post comment: %s", - "Sending paste…": "Sending paste…", + "Sending paste…": "Enviant paste…", "Your paste is %s (Hit [Ctrl]+[c] to copy)": "Your paste is %s (Hit [Ctrl]+[c] to copy)", - "Delete data": "Delete data", + "Delete data": "Esborrar les dades", "Could not create paste: %s": "Could not create paste: %s", "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)", "B": "B", @@ -140,10 +140,10 @@ "ZiB": "ZiB", "YiB": "YiB", "Format": "Format", - "Plain Text": "Plain Text", - "Source Code": "Source Code", + "Plain Text": "Text sense format", + "Source Code": "Codi font", "Markdown": "Markdown", - "Download attachment": "Download attachment", + "Download attachment": "Baixar els adjunts", "Cloned: '%s'": "Cloned: '%s'", "The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.", "Attach a file": "Attach a file", From 7277d2bb435c34a6979684f0a77446d727bfdabf Mon Sep 17 00:00:00 2001 From: El RIDO Date: Fri, 18 Feb 2022 07:36:09 +0100 Subject: [PATCH 246/478] update all libraries --- CHANGELOG.md | 2 +- composer.json | 8 +- composer.lock | 84 +++++---- js/{base-x-3.0.7.js => base-x-4.0.0.js} | 22 ++- js/common.js | 6 +- js/purify-2.2.7.js | 2 - js/purify-2.3.6.js | 2 + js/showdown-1.9.1.js | 2 - js/showdown-2.0.0.js | 2 + tpl/bootstrap.php | 8 +- tpl/page.php | 8 +- vendor/composer/autoload_classmap.php | 5 +- vendor/composer/autoload_static.php | 5 +- .../ip-lib/src/Address/AddressInterface.php | 31 +++ .../ip-lib/src/Address/AssignedRange.php | 2 + vendor/mlocati/ip-lib/src/Address/IPv4.php | 178 +++++++++++------- vendor/mlocati/ip-lib/src/Address/IPv6.php | 128 ++++++++----- vendor/mlocati/ip-lib/src/Address/Type.php | 2 + vendor/mlocati/ip-lib/src/Factory.php | 149 ++++++++++++--- vendor/mlocati/ip-lib/src/ParseStringFlag.php | 79 ++++++++ .../ip-lib/src/Range/AbstractRange.php | 32 +++- vendor/mlocati/ip-lib/src/Range/Pattern.php | 71 +++++-- .../ip-lib/src/Range/RangeInterface.php | 40 ++++ vendor/mlocati/ip-lib/src/Range/Single.php | 58 ++++-- vendor/mlocati/ip-lib/src/Range/Subnet.php | 63 ++++++- vendor/mlocati/ip-lib/src/Range/Type.php | 2 + .../mlocati/ip-lib/src/Service/BinaryMath.php | 2 + ...r.php => RangesFromBoundaryCalculator.php} | 19 +- .../src/Service/UnsignedIntegerMath.php | 171 +++++++++++++++++ vendor/paragonie/random_compat/lib/random.php | 2 +- 30 files changed, 928 insertions(+), 257 deletions(-) rename js/{base-x-3.0.7.js => base-x-4.0.0.js} (87%) delete mode 100644 js/purify-2.2.7.js create mode 100644 js/purify-2.3.6.js delete mode 100644 js/showdown-1.9.1.js create mode 100644 js/showdown-2.0.0.js create mode 100644 vendor/mlocati/ip-lib/src/ParseStringFlag.php rename vendor/mlocati/ip-lib/src/Service/{RangesFromBounradyCalculator.php => RangesFromBoundaryCalculator.php} (89%) create mode 100644 vendor/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c3f804..a877e8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ * ADDED: Google Cloud Storage backend support (#795) * ADDED: Oracle database support (#868) * CHANGED: Language selection cookie only transmitted over HTTPS (#472) - * CHANGED: Upgrading libraries to: random_compat 2.0.20 + * CHANGED: Upgrading libraries to: base-x 4.0.0, DOMpurify 2.3.6, ip-lib 1.18.0, jQuery 3.6.0, random_compat 2.0.21 & Showdown 2.0.0 * CHANGED: Removed automatic `.ini` configuration file migration (#808) * CHANGED: Removed configurable `dir` for `traffic` & `purge` limiters (#419) * CHANGED: Server salt, traffic and purge limiter now stored in the storage backend (#419) diff --git a/composer.json b/composer.json index 7b09fc3..0fd3455 100644 --- a/composer.json +++ b/composer.json @@ -25,12 +25,12 @@ }, "require" : { "php" : "^5.6.0 || ^7.0 || ^8.0", - "paragonie/random_compat" : "2.0.20", + "paragonie/random_compat" : "2.0.21", "yzalis/identicon" : "2.0.0", - "mlocati/ip-lib" : "1.14.0" + "mlocati/ip-lib" : "1.18.0" }, "suggest" : { - "google/cloud-storage" : "1.23.1" + "google/cloud-storage" : "1.26.1" }, "require-dev" : { "phpunit/phpunit" : "^4.6 || ^5.0" @@ -43,4 +43,4 @@ "config" : { "autoloader-suffix" : "DontChange" } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 7a60520..a159dd8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "217f0ba9bdac1014a332a8ba390be949", + "content-hash": "fa52d4988bfe17d4b27e3a4789a1ec49", "packages": [ { "name": "mlocati/ip-lib", - "version": "1.14.0", + "version": "1.18.0", "source": { "type": "git", "url": "https://github.com/mlocati/ip-lib.git", - "reference": "882bc0e115970a536b13bcfa59f312783fce08c8" + "reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/882bc0e115970a536b13bcfa59f312783fce08c8", - "reference": "882bc0e115970a536b13bcfa59f312783fce08c8", + "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2", + "reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2", "shasum": "" }, "require": { @@ -25,8 +25,7 @@ }, "require-dev": { "ext-pdo_sqlite": "*", - "phpunit/dbunit": "^1.4 || ^2 || ^3 || ^4", - "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.5 || ^9.5" }, "type": "library", "autoload": { @@ -72,27 +71,27 @@ "type": "other" } ], - "time": "2020-12-31T11:30:02+00:00" + "time": "2022-01-13T18:05:33+00:00" }, { "name": "paragonie/random_compat", - "version": "v2.0.20", + "version": "v2.0.21", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a" + "reference": "96c132c7f2f7bc3230723b66e89f8f150b29d5ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0f1f60250fccffeaf5dda91eea1c018aed1adc2a", - "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/96c132c7f2f7bc3230723b66e89f8f150b29d5ae", + "reference": "96c132c7f2f7bc3230723b66e89f8f150b29d5ae", "shasum": "" }, "require": { "php": ">=5.2.0" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*" + "phpunit/phpunit": "*" }, "suggest": { "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." @@ -121,7 +120,7 @@ "pseudorandom", "random" ], - "time": "2021-04-17T09:33:01+00:00" + "time": "2022-02-16T17:07:03+00:00" }, { "name": "yzalis/identicon", @@ -270,12 +269,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -348,16 +347,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", "shasum": "" }, "require": { @@ -368,7 +367,8 @@ "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2" + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -396,20 +396,20 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2020-09-03T19:13:55+00:00" + "time": "2021-10-19T17:43:47+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706", + "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706", "shasum": "" }, "require": { @@ -417,7 +417,8 @@ "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "*" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -441,7 +442,7 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2020-09-17T18:55:26+00:00" + "time": "2022-01-04T19:58:01+00:00" }, { "name": "phpspec/prophecy", @@ -1419,21 +1420,24 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.23.0", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + "reference": "30885182c981ab175d4d034db0f6f469898070ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-ctype": "*" + }, "suggest": { "ext-ctype": "For best performance" }, @@ -1491,20 +1495,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2021-10-20T20:35:02+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.24", + "version": "v4.4.37", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "8b6d1b97521e2f125039b3fcb4747584c6dfa0ef" + "reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/8b6d1b97521e2f125039b3fcb4747584c6dfa0ef", - "reference": "8b6d1b97521e2f125039b3fcb4747584c6dfa0ef", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d7f637cc0f0cc14beb0984f2bb50da560b271311", + "reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311", "shasum": "" }, "require": { @@ -1559,7 +1563,7 @@ "type": "tidelift" } ], - "time": "2021-05-16T09:52:47+00:00" + "time": "2022-01-24T20:11:01+00:00" }, { "name": "webmozart/assert", diff --git a/js/base-x-3.0.7.js b/js/base-x-4.0.0.js similarity index 87% rename from js/base-x-3.0.7.js rename to js/base-x-4.0.0.js index 7608d2e..0a83978 100644 --- a/js/base-x-3.0.7.js +++ b/js/base-x-4.0.0.js @@ -1,17 +1,16 @@ 'use strict'; // base-x encoding / decoding -// based on https://github.com/cryptocoinjs/base-x 3.0.7 -// modification: removed Buffer dependency and node.modules entry // Copyright (c) 2018 base-x contributors // Copyright (c) 2014-2018 The Bitcoin Core developers (base58.cpp) // Distributed under the MIT software license, see the accompanying // file LICENSE or http://www.opensource.org/licenses/mit-license.php. - (function(){ this.baseX = function base (ALPHABET) { if (ALPHABET.length >= 255) { throw new TypeError('Alphabet too long') } var BASE_MAP = new Uint8Array(256) - BASE_MAP.fill(255) + for (var j = 0; j < BASE_MAP.length; j++) { + BASE_MAP[j] = 255 + } for (var i = 0; i < ALPHABET.length; i++) { var x = ALPHABET.charAt(i) var xc = x.charCodeAt(0) @@ -23,6 +22,13 @@ this.baseX = function base (ALPHABET) { var FACTOR = Math.log(BASE) / Math.log(256) // log(BASE) / log(256), rounded up var iFACTOR = Math.log(256) / Math.log(BASE) // log(256) / log(BASE), rounded up function encode (source) { + if (source instanceof Uint8Array) { + } else if (ArrayBuffer.isView(source)) { + source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength) + } else if (Array.isArray(source)) { + source = Uint8Array.from(source) + } + if (!(source instanceof Uint8Array)) { throw new TypeError('Expected Uint8Array') } if (source.length === 0) { return '' } // Skip & count leading zeroes. var zeroes = 0 @@ -62,10 +68,8 @@ this.baseX = function base (ALPHABET) { } function decodeUnsafe (source) { if (typeof source !== 'string') { throw new TypeError('Expected String') } - if (source.length === 0) { return '' } + if (source.length === 0) { return new Uint8Array() } var psz = 0 - // Skip leading spaces. - if (source[psz] === ' ') { return } // Skip and count leading '1's. var zeroes = 0 var length = 0 @@ -92,14 +96,12 @@ this.baseX = function base (ALPHABET) { length = i psz++ } - // Skip trailing spaces. - if (source[psz] === ' ') { return } // Skip leading zeroes in b256. var it4 = size - length while (it4 !== size && b256[it4] === 0) { it4++ } - var vch = [] + var vch = new Uint8Array(zeroes + (size - it4)) var j = zeroes while (it4 !== size) { vch[j++] = b256[it4++] diff --git a/js/common.js b/js/common.js index 4acbbfc..7806948 100644 --- a/js/common.js +++ b/js/common.js @@ -16,9 +16,9 @@ global.zlib = require('./zlib-1.2.11').zlib; require('./prettify'); global.prettyPrint = window.PR.prettyPrint; global.prettyPrintOne = window.PR.prettyPrintOne; -global.showdown = require('./showdown-1.9.1'); -global.DOMPurify = require('./purify-2.2.7'); -global.baseX = require('./base-x-3.0.7').baseX; +global.showdown = require('./showdown-2.0.0'); +global.DOMPurify = require('./purify-2.3.6'); +global.baseX = require('./base-x-4.0.0').baseX; global.Legacy = require('./legacy').Legacy; require('./bootstrap-3.3.7'); require('./privatebin'); diff --git a/js/purify-2.2.7.js b/js/purify-2.2.7.js deleted file mode 100644 index e9e4bad..0000000 --- a/js/purify-2.2.7.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.2.2/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.getPrototypeOf,o=Object.getOwnPropertyDescriptor,i=Object.freeze,a=Object.seal,l=Object.create,c="undefined"!=typeof Reflect&&Reflect,s=c.apply,u=c.construct;s||(s=function(e,t,n){return e.apply(t,n)}),i||(i=function(e){return e}),a||(a=function(e){return e}),u||(u=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=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/purify-2.3.6.js b/js/purify-2.3.6.js new file mode 100644 index 0000000..1d0a593 --- /dev/null +++ b/js/purify-2.3.6.js @@ -0,0 +1,2 @@ +/*! @license DOMPurify 2.3.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.6/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.getPrototypeOf,o=Object.getOwnPropertyDescriptor,i=Object.freeze,a=Object.seal,l=Object.create,c="undefined"!=typeof Reflect&&Reflect,s=c.apply,u=c.construct;s||(s=function(e,t,n){return e.apply(t,n)}),i||(i=function(e){return e}),a||(a=function(e){return e}),u||(u=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),z=a(/^data-[\-\w.\u00B7-\uFFFF]/),B=a(/^aria-[\-\w]+$/),P=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),j=a(/^(?:\w+script|data):/i),G=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),W=a(/^html$/i),q="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 Y(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.3.6",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,m=t.NamedNodeMap,A=void 0===m?t.NamedNodeMap||t.MozNamedAttrMap:m,$=t.HTMLFormElement,X=t.DOMParser,Z=t.trustedTypes,J=s.prototype,Q=w(J,"cloneNode"),ee=w(J,"nextSibling"),te=w(J,"childNodes"),ne=w(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?oe.createHTML(""):"",ae=o,le=ae.implementation,ce=ae.createNodeIterator,se=ae.createDocumentFragment,ue=ae.getElementsByTagName,me=r.importNode,fe={};try{fe=x(o).documentMode?o.documentMode:{}}catch(e){}var de={};n.isSupported="function"==typeof ne&&le&&void 0!==le.createHTMLDocument&&9!==fe;var pe=H,he=U,ge=z,ye=B,ve=j,be=G,Te=P,Ne=null,Ae=E({},[].concat(Y(k),Y(S),Y(_),Y(O),Y(M))),Ee=null,xe=E({},[].concat(Y(L),Y(R),Y(I),Y(F))),we=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),ke=null,Se=null,_e=!0,De=!0,Oe=!1,Ce=!1,Me=!1,Le=!1,Re=!1,Ie=!1,Fe=!1,He=!1,Ue=!0,ze=!0,Be=!1,Pe={},je=null,Ge=E({},["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,qe=E({},["audio","video","img","source","image","track"]),Ye=null,Ke=E({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ve="http://www.w3.org/1998/Math/MathML",$e="http://www.w3.org/2000/svg",Xe="http://www.w3.org/1999/xhtml",Ze=Xe,Je=!1,Qe=void 0,et=["application/xhtml+xml","text/html"],tt="text/html",nt=void 0,rt=null,ot=o.createElement("form"),it=function(e){return e instanceof RegExp||e instanceof Function},at=function(e){rt&&rt===e||(e&&"object"===(void 0===e?"undefined":q(e))||(e={}),e=x(e),Ne="ALLOWED_TAGS"in e?E({},e.ALLOWED_TAGS):Ae,Ee="ALLOWED_ATTR"in e?E({},e.ALLOWED_ATTR):xe,Ye="ADD_URI_SAFE_ATTR"in e?E(x(Ke),e.ADD_URI_SAFE_ATTR):Ke,We="ADD_DATA_URI_TAGS"in e?E(x(qe),e.ADD_DATA_URI_TAGS):qe,je="FORBID_CONTENTS"in e?E({},e.FORBID_CONTENTS):Ge,ke="FORBID_TAGS"in e?E({},e.FORBID_TAGS):{},Se="FORBID_ATTR"in e?E({},e.FORBID_ATTR):{},Pe="USE_PROFILES"in e&&e.USE_PROFILES,_e=!1!==e.ALLOW_ARIA_ATTR,De=!1!==e.ALLOW_DATA_ATTR,Oe=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Ce=e.SAFE_FOR_TEMPLATES||!1,Me=e.WHOLE_DOCUMENT||!1,Ie=e.RETURN_DOM||!1,Fe=e.RETURN_DOM_FRAGMENT||!1,He=e.RETURN_TRUSTED_TYPE||!1,Re=e.FORCE_BODY||!1,Ue=!1!==e.SANITIZE_DOM,ze=!1!==e.KEEP_CONTENT,Be=e.IN_PLACE||!1,Te=e.ALLOWED_URI_REGEXP||Te,Ze=e.NAMESPACE||Xe,e.CUSTOM_ELEMENT_HANDLING&&it(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(we.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&it(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(we.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(we.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Qe=Qe=-1===et.indexOf(e.PARSER_MEDIA_TYPE)?tt:e.PARSER_MEDIA_TYPE,nt="application/xhtml+xml"===Qe?function(e){return e}:h,Ce&&(De=!1),Fe&&(Ie=!0),Pe&&(Ne=E({},[].concat(Y(M))),Ee=[],!0===Pe.html&&(E(Ne,k),E(Ee,L)),!0===Pe.svg&&(E(Ne,S),E(Ee,R),E(Ee,F)),!0===Pe.svgFilters&&(E(Ne,_),E(Ee,R),E(Ee,F)),!0===Pe.mathMl&&(E(Ne,O),E(Ee,I),E(Ee,F))),e.ADD_TAGS&&(Ne===Ae&&(Ne=x(Ne)),E(Ne,e.ADD_TAGS)),e.ADD_ATTR&&(Ee===xe&&(Ee=x(Ee)),E(Ee,e.ADD_ATTR)),e.ADD_URI_SAFE_ATTR&&E(Ye,e.ADD_URI_SAFE_ATTR),e.FORBID_CONTENTS&&(je===Ge&&(je=x(je)),E(je,e.FORBID_CONTENTS)),ze&&(Ne["#text"]=!0),Me&&E(Ne,["html","head","body"]),Ne.table&&(E(Ne,["tbody"]),delete ke.tbody),i&&i(e),rt=e)},lt=E({},["mi","mo","mn","ms","mtext"]),ct=E({},["foreignobject","desc","title","annotation-xml"]),st=E({},S);E(st,_),E(st,D);var ut=E({},O);E(ut,C);var mt=function(e){var t=ne(e);t&&t.tagName||(t={namespaceURI:Xe,tagName:"template"});var n=h(e.tagName),r=h(t.tagName);if(e.namespaceURI===$e)return t.namespaceURI===Xe?"svg"===n:t.namespaceURI===Ve?"svg"===n&&("annotation-xml"===r||lt[r]):Boolean(st[n]);if(e.namespaceURI===Ve)return t.namespaceURI===Xe?"math"===n:t.namespaceURI===$e?"math"===n&&ct[r]:Boolean(ut[n]);if(e.namespaceURI===Xe){if(t.namespaceURI===$e&&!ct[r])return!1;if(t.namespaceURI===Ve&&!lt[r])return!1;var o=E({},["title","style","font","a","script"]);return!ut[n]&&(o[n]||!st[n])}return!1},ft=function(e){p(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){try{e.outerHTML=ie}catch(t){e.remove()}}},dt=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&&!Ee[e])if(Ie||Fe)try{ft(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},pt=function(e){var t=void 0,n=void 0;if(Re)e=""+e;else{var r=g(e,/^[\r\n\t ]+/);n=r&&r[0]}"application/xhtml+xml"===Qe&&(e=''+e+"");var i=oe?oe.createHTML(e):e;if(Ze===Xe)try{t=(new X).parseFromString(i,Qe)}catch(e){}if(!t||!t.documentElement){t=le.createDocument(Ze,"template",null);try{t.documentElement.innerHTML=Je?"":i}catch(e){}}var a=t.body||t.documentElement;return e&&n&&a.insertBefore(o.createTextNode(n),a.childNodes[0]||null),Ze===Xe?ue.call(t,Me?"html":"body")[0]:Me?t.documentElement:a},ht=function(e){return ce.call(e.ownerDocument||e,e,u.SHOW_ELEMENT|u.SHOW_COMMENT|u.SHOW_TEXT,null,!1)},gt=function(e){return e instanceof $&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof A)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore)},yt=function(e){return"object"===(void 0===c?"undefined":q(c))?e instanceof c:e&&"object"===(void 0===e?"undefined":q(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},vt=function(e,t,r){de[e]&&f(de[e],(function(e){e.call(n,t,r,rt)}))},bt=function(e){var t=void 0;if(vt("beforeSanitizeElements",e,null),gt(e))return ft(e),!0;if(g(e.nodeName,/[\u0080-\uFFFF]/))return ft(e),!0;var r=nt(e.nodeName);if(vt("uponSanitizeElement",e,{tagName:r,allowedTags:Ne}),!yt(e.firstElementChild)&&(!yt(e.content)||!yt(e.content.firstElementChild))&&T(/<[/\w]/g,e.innerHTML)&&T(/<[/\w]/g,e.textContent))return ft(e),!0;if("select"===r&&T(/