mirror of
https://github.com/PrivateBin/PrivateBin.git
synced 2024-03-22 13:10:41 +08:00
Merge branch 'master' into php8
This commit is contained in:
commit
46c0fc851c
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -1,3 +1,5 @@
|
|||
bin/configuration-test-generator export-ignore
|
||||
bin/icon-test export-ignore
|
||||
doc/ export-ignore
|
||||
tst/ export-ignore
|
||||
img/browserstack.svg export-ignore
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# PrivateBin version history
|
||||
|
||||
* **1.4.1 (not yet released)**
|
||||
* ADDED: Translations for Turkish, Slovak and Greek
|
||||
* **1.5 (not yet released)**
|
||||
* ADDED: script for data storage backend migrations (#1012)
|
||||
* ADDED: Translations for Turkish, Slovak, Greek and Thai
|
||||
* ADDED: S3 Storage backend (#994)
|
||||
* CHANGED: Switched to Jdenticons as the default for comment icons (#793)
|
||||
* ADDED: Jdenticons as an option for comment icons (#793)
|
||||
* CHANGED: Avoid `SUPER` privilege for setting the `sql_mode` for MariaDB/MySQL (#919)
|
||||
* CHANGED: Upgrading libraries to: zlib 1.2.13
|
||||
* CHANGED: Upgrading libraries to: DOMpurify 2.4.6, jQuery 3.6.1, Showdown 2.1.0 & zlib 1.2.13
|
||||
* FIXED: Revert to CREATE INDEX without IF NOT EXISTS clauses, to support MySQL (#943)
|
||||
* FIXED: Apply table prefix to indexes as well, to support multiple instances sharing a single database (#943)
|
||||
* FIXED: YOURLS integration via new proxy, storing signature in configuration (#725)
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
* Austin Huang - Oracle database support
|
||||
* Felix J. Ogris - S3 Storage backend
|
||||
* Mounir Idrassi & J. Mozdzen - secure YOURLS integration
|
||||
* Felix J. Ogris - script for data backend migrations, dropped singleton behaviour of data backends
|
||||
|
||||
## Translations
|
||||
* Hexalyse - French
|
||||
|
@ -61,3 +62,4 @@
|
|||
* Emir Ensar Rahmanlar - Turkish
|
||||
* Stevo984 - Slovak
|
||||
* Christos Karamolegkos - Greek
|
||||
* jaideejung007 - Thai
|
||||
|
|
10
INSTALL.md
10
INSTALL.md
|
@ -26,7 +26,7 @@ install and configure PrivateBin on your server. It's available on
|
|||
- `open_basedir` access to `/dev/urandom`
|
||||
- mcrypt extension AND `open_basedir` access to `/dev/urandom`
|
||||
- com_dotnet extension
|
||||
- GD extension
|
||||
- GD extension (when using identicon or vizhash icons, jdenticon works without it)
|
||||
- zlib extension
|
||||
- some disk space or a database supported by [PDO](https://php.net/manual/book.pdo.php)
|
||||
- ability to create files and folders in the installation directory and the PATH
|
||||
|
@ -38,10 +38,10 @@ install and configure PrivateBin on your server. It's available on
|
|||
### Changing the Path
|
||||
|
||||
In the index.php you can define a different `PATH`. This is useful to secure
|
||||
your installation. You can move the configuration, data files, templates and PHP
|
||||
libraries (directories cfg, doc, data, lib, tpl, tst and vendor) outside of your
|
||||
document root. This new location must still be accessible to your webserver and
|
||||
PHP process (see also
|
||||
your installation. You can move the utilities, configuration, data files,
|
||||
templates and PHP libraries (directories bin, cfg, doc, data, lib, tpl, tst and
|
||||
vendor) outside of your document root. This new location must still be
|
||||
accessible to your webserver and PHP process (see also
|
||||
[open_basedir setting](https://secure.php.net/manual/en/ini.core.php#ini.open-basedir)).
|
||||
|
||||
> #### PATH Example
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
* DANGER: Too many options/settings and too high max iteration setting may trigger
|
||||
* a fork bomb. Please save your work before executing this script.
|
||||
*/
|
||||
include 'Bootstrap.php';
|
||||
|
||||
define('PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
|
||||
include PATH . 'tst' . DIRECTORY_SEPARATOR . 'Bootstrap.php';
|
||||
|
||||
$vd = array('view', 'delete');
|
||||
$vcd = array('view', 'create', 'delete');
|
||||
|
@ -392,7 +394,7 @@ class ConfigurationTestGenerator
|
|||
}
|
||||
}
|
||||
$code .= '}' . PHP_EOL;
|
||||
file_put_contents('ConfigurationCombinationsTest.php', $code);
|
||||
file_put_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'tst' . DIRECTORY_SEPARATOR . 'ConfigurationCombinationsTest.php', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -428,7 +430,9 @@ class ConfigurationCombinationsTest extends TestCase
|
|||
/* Setup Routine */
|
||||
Helper::confBackup();
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
|
||||
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
|
||||
$this->_model = new Filesystem(array('dir' => $this->_path));
|
||||
ServerSalt::setStore($this->_model);
|
||||
TrafficLimiter::setStore($this->_model);
|
||||
$this->reset();
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ use Identicon\Generator\GdGenerator;
|
|||
use Identicon\Generator\ImageMagickGenerator;
|
||||
use Identicon\Generator\SvgGenerator;
|
||||
use Identicon\Identicon;
|
||||
use Jdenticon\Identicon as Jdenticon;
|
||||
use PrivateBin\Vizhash16x16;
|
||||
|
||||
|
||||
|
@ -17,7 +18,19 @@ $vizhash = new Vizhash16x16();
|
|||
$identiconGenerators = array(
|
||||
'identicon GD' => new Identicon(new GdGenerator()),
|
||||
'identicon ImageMagick' => new Identicon(new ImageMagickGenerator()),
|
||||
'identicon SVG' => new Identicon(new SvgGenerator())
|
||||
'identicon SVG' => new Identicon(new SvgGenerator()),
|
||||
);
|
||||
$jdenticon = new Jdenticon(array(
|
||||
'size' => 16,
|
||||
'style' => array(
|
||||
'backgroundColor' => '#fff0', // fully transparent, for dark mode
|
||||
'padding' => 0,
|
||||
),
|
||||
));
|
||||
$jdenticonGenerators = array(
|
||||
'jdenticon' => 'png',
|
||||
'jdenticon ImageMagick' => 'png',
|
||||
'jdenticon SVG' => 'svg',
|
||||
);
|
||||
$results = array(
|
||||
'vizhash' => array(
|
||||
|
@ -35,21 +48,30 @@ $results = array(
|
|||
'identicon SVG' => array(
|
||||
'lengths' => array(),
|
||||
'time' => 0
|
||||
)
|
||||
),
|
||||
'jdenticon' => array(
|
||||
'lengths' => array(),
|
||||
'time' => 0
|
||||
),
|
||||
'jdenticon ImageMagick' => array(
|
||||
'lengths' => array(),
|
||||
'time' => 0
|
||||
),
|
||||
'jdenticon SVG' => array(
|
||||
'lengths' => array(),
|
||||
'time' => 0
|
||||
),
|
||||
);
|
||||
$hmacs = array();
|
||||
|
||||
echo 'generate ', ITERATIONS, ' hmacs and pre-populate the result array, so tests wont be slowed down', PHP_EOL;
|
||||
for ($i = 0; $i < ITERATIONS; ++$i) {
|
||||
$hmacs[$i] = hash_hmac('sha512', '127.0.0.1', bin2hex(random_bytes(256)));
|
||||
$results['vizhash']['lengths'][$i] = 0;
|
||||
$results['identicon GD']['lengths'][$i] = 0;
|
||||
$results['identicon ImageMagick']['lengths'][$i] = 0;
|
||||
$results['identicon SVG']['lengths'][$i] = 0;
|
||||
foreach (array_keys($results) as $test) {
|
||||
$results[$test]['lengths'][$i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
echo 'run vizhash tests', PHP_EOL;
|
||||
$start = microtime(true);
|
||||
foreach ($hmacs as $i => $hmac) {
|
||||
|
@ -60,7 +82,6 @@ foreach ($hmacs as $i => $hmac) {
|
|||
}
|
||||
$results['vizhash']['time'] = microtime(true) - $start;
|
||||
|
||||
|
||||
foreach ($identiconGenerators as $key => $identicon) {
|
||||
echo 'run ', $key,' tests', PHP_EOL;
|
||||
$start = microtime(true);
|
||||
|
@ -71,9 +92,35 @@ foreach ($identiconGenerators as $key => $identicon) {
|
|||
$results[$key]['time'] = microtime(true) - $start;
|
||||
}
|
||||
|
||||
foreach ($jdenticonGenerators as $key => $format) {
|
||||
echo 'run ', $key,' tests', PHP_EOL;
|
||||
if ($key === 'jdenticon ImageMagick') {
|
||||
$jdenticon->enableImageMagick = true;
|
||||
} else {
|
||||
$jdenticon->enableImageMagick = false;
|
||||
}
|
||||
$start = microtime(true);
|
||||
foreach ($hmacs as $i => $hmac) {
|
||||
$jdenticon->setHash($hmac);
|
||||
$data = $jdenticon->getImageDataUri($format);
|
||||
$results[$key]['lengths'][$i] = strlen($data);
|
||||
}
|
||||
$results[$key]['time'] = microtime(true) - $start;
|
||||
}
|
||||
|
||||
|
||||
define('PADDING_LENGTH', max(array_map(function ($key) { return strlen($key); }, array_keys($results))) + 1);
|
||||
define(
|
||||
'PADDING_LENGTH',
|
||||
max(
|
||||
array_map(
|
||||
function ($key) {
|
||||
return strlen($key);
|
||||
},
|
||||
array_keys($results)
|
||||
)
|
||||
) + 1
|
||||
);
|
||||
|
||||
function format_result_line($generator, $min, $max, $avg, $sec) {
|
||||
echo str_pad($generator, PADDING_LENGTH, ' '), "\t",
|
||||
str_pad($min, 4, ' ', STR_PAD_LEFT), "\t",
|
||||
|
@ -84,7 +131,10 @@ function format_result_line($generator, $min, $max, $avg, $sec) {
|
|||
|
||||
echo PHP_EOL;
|
||||
format_result_line('Generator:', 'min', 'max', 'avg', 'seconds');
|
||||
format_result_line(str_repeat('─', PADDING_LENGTH), str_repeat('─', 4), str_repeat('─', 4), str_repeat('─', 4), str_repeat('─', 7));
|
||||
format_result_line(
|
||||
str_repeat('─', PADDING_LENGTH), str_repeat('─', 4), str_repeat('─', 4),
|
||||
str_repeat('─', 4), str_repeat('─', 7)
|
||||
);
|
||||
foreach ($results as $generator => $result) {
|
||||
sort($result['lengths']);
|
||||
format_result_line(
|
199
bin/migrate
Executable file
199
bin/migrate
Executable file
|
@ -0,0 +1,199 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
// change this, if your php files and data is outside of your webservers document root
|
||||
define('PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
|
||||
|
||||
define('PUBLIC_PATH', __DIR__ . DIRECTORY_SEPARATOR);
|
||||
require PATH . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
||||
|
||||
use PrivateBin\Configuration;
|
||||
use PrivateBin\Model;
|
||||
|
||||
// third argument in getopt requires PHP >= 7.1
|
||||
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
|
||||
dieerr('migrate requires php 7.1 or above to work. Sorry.');
|
||||
}
|
||||
|
||||
$longopts = array(
|
||||
"delete-after",
|
||||
"delete-during"
|
||||
);
|
||||
$opts_arr = getopt("fhnv", $longopts, $rest);
|
||||
if ($opts_arr === false) {
|
||||
dieerr("Erroneous command line options. Please use -h");
|
||||
}
|
||||
if (array_key_exists("h", $opts_arr)) {
|
||||
helpexit();
|
||||
}
|
||||
|
||||
$delete_after = array_key_exists("delete-after", $opts_arr);
|
||||
$delete_during = array_key_exists("delete-during", $opts_arr);
|
||||
$force_overwrite = array_key_exists("f", $opts_arr);
|
||||
$dryrun = array_key_exists("n", $opts_arr);
|
||||
$verbose = array_key_exists("v", $opts_arr);
|
||||
|
||||
if ($rest >= $argc) {
|
||||
dieerr("Missing source configuration directory");
|
||||
}
|
||||
if ($delete_after && $delete_during) {
|
||||
dieerr("--delete-after and --delete-during are mutually exclusive");
|
||||
}
|
||||
|
||||
$srcconf = getConfig("source", $argv[$rest]);
|
||||
$rest++;
|
||||
$dstconf = getConfig("destination", ($rest < $argc ? $argv[$rest] : ""));
|
||||
|
||||
if (($srcconf->getSection("model") == $dstconf->getSection("model")) &&
|
||||
($srcconf->getSection("model_options") == $dstconf->getSection("model_options"))) {
|
||||
dieerr("Source and destination storage configurations are identical");
|
||||
}
|
||||
|
||||
$srcmodel = new Model($srcconf);
|
||||
$srcstore = $srcmodel->getStore();
|
||||
$dstmodel = new Model($dstconf);
|
||||
$dststore = $dstmodel->getStore();
|
||||
$ids = $srcstore->getAllPastes();
|
||||
|
||||
foreach ($ids as $id) {
|
||||
debug("Reading paste id " . $id);
|
||||
$paste = $srcstore->read($id);
|
||||
$comments = $srcstore->readComments($id);
|
||||
|
||||
savePaste($force_overwrite, $dryrun, $id, $paste, $dststore);
|
||||
foreach ($comments as $comment) {
|
||||
saveComment($force_overwrite, $dryrun, $id, $comment, $dststore);
|
||||
}
|
||||
if ($delete_during) {
|
||||
deletePaste($dryrun, $id, $srcstore);
|
||||
}
|
||||
}
|
||||
|
||||
if ($delete_after) {
|
||||
foreach ($ids as $id) {
|
||||
deletePaste($dryrun, $id, $srcstore);
|
||||
}
|
||||
}
|
||||
|
||||
debug("Done.");
|
||||
|
||||
|
||||
function deletePaste($dryrun, $pasteid, $srcstore)
|
||||
{
|
||||
if (!$dryrun) {
|
||||
debug("Deleting paste id " . $pasteid);
|
||||
$srcstore->delete($pasteid);
|
||||
} else {
|
||||
debug("Would delete paste id " . $pasteid);
|
||||
}
|
||||
}
|
||||
|
||||
function saveComment ($force_overwrite, $dryrun, $pasteid, $comment, $dststore)
|
||||
{
|
||||
$parentid = $comment["parentid"];
|
||||
$commentid = $comment["id"];
|
||||
|
||||
if (!$dststore->existsComment($pasteid, $parentid, $commentid)) {
|
||||
if (!$dryrun) {
|
||||
debug("Saving paste id " . $pasteid . ", parent id " .
|
||||
$parentid . ", comment id " . $commentid);
|
||||
$dststore->createComment($pasteid, $parentid, $commentid, $comment);
|
||||
} else {
|
||||
debug("Would save paste id " . $pasteid . ", parent id " .
|
||||
$parentid . ", comment id " . $commentid);
|
||||
}
|
||||
} else if ($force_overwrite) {
|
||||
if (!$dryrun) {
|
||||
debug("Overwriting paste id " . $pasteid . ", parent id " .
|
||||
$parentid . ", comment id " . $commentid);
|
||||
$dststore->createComment($pasteid, $parentid, $commentid, $comment);
|
||||
} else {
|
||||
debug("Would overwrite paste id " . $pasteid . ", parent id " .
|
||||
$parentid . ", comment id " . $commentid);
|
||||
}
|
||||
} else {
|
||||
if (!$dryrun) {
|
||||
dieerr("Not overwriting paste id " . $pasteid . ", parent id " .
|
||||
$parentid . ", comment id " . $commentid);
|
||||
} else {
|
||||
dieerr("Would not overwrite paste id " . $pasteid . ", parent id " .
|
||||
$parentid . ", comment id " . $commentid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function savePaste ($force_overwrite, $dryrun, $pasteid, $paste, $dststore)
|
||||
{
|
||||
if (!$dststore->exists($pasteid)) {
|
||||
if (!$dryrun) {
|
||||
debug("Saving paste id " . $pasteid);
|
||||
$dststore->create($pasteid, $paste);
|
||||
} else {
|
||||
debug("Would save paste id " . $pasteid);
|
||||
}
|
||||
} else if ($force_overwrite) {
|
||||
if (!$dryrun) {
|
||||
debug("Overwriting paste id " . $pasteid);
|
||||
$dststore->create($pasteid, $paste);
|
||||
} else {
|
||||
debug("Would overwrite paste id " . $pasteid);
|
||||
}
|
||||
} else {
|
||||
if (!$dryrun) {
|
||||
dieerr("Not overwriting paste id " . $pasteid);
|
||||
} else {
|
||||
dieerr("Would not overwrite paste id " . $pasteid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getConfig ($target, $confdir)
|
||||
{
|
||||
debug("Trying to load " . $target . " conf.php" .
|
||||
($confdir === "" ? "" : " from " . $confdir));
|
||||
|
||||
putenv("CONFIG_PATH=" . $confdir);
|
||||
$conf = new Configuration;
|
||||
putenv("CONFIG_PATH=");
|
||||
|
||||
return $conf;
|
||||
}
|
||||
|
||||
function dieerr ($text)
|
||||
{
|
||||
fprintf(STDERR, "ERROR: %s" . PHP_EOL, $text);
|
||||
die(1);
|
||||
}
|
||||
|
||||
function debug ($text) {
|
||||
if ($GLOBALS["verbose"]) {
|
||||
printf("DEBUG: %s" . PHP_EOL, $text);
|
||||
}
|
||||
}
|
||||
|
||||
function helpexit ()
|
||||
{
|
||||
print("migrate.php - Copy data between PrivateBin backends
|
||||
|
||||
Usage:
|
||||
migrate [--delete-after] [--delete-during] [-f] [-n] [-v] srcconfdir
|
||||
[<dstconfdir>]
|
||||
migrate [-h]
|
||||
|
||||
Options:
|
||||
--delete-after delete data from source after all pastes and comments have
|
||||
successfully been copied to the destination
|
||||
--delete-during delete data from source after the current paste and its
|
||||
comments have successfully been copied to the destination
|
||||
-f forcefully overwrite data which already exists at the
|
||||
destination
|
||||
-n dry run, do not copy data
|
||||
-v be verbose
|
||||
<srcconfdir> use storage backend configration from conf.php found in
|
||||
this directory as source
|
||||
<dstconfdir> optionally, use storage backend configration from conf.php
|
||||
found in this directory as destination; defaults to:
|
||||
" . PATH . "cfg" . DIRECTORY_SEPARATOR . "conf.php
|
||||
");
|
||||
exit();
|
||||
}
|
|
@ -70,7 +70,7 @@ languageselection = false
|
|||
; used to get the IP of a comment poster if the server salt is leaked and a
|
||||
; SHA512 HMAC rainbow table is generated for all (relevant) IPs.
|
||||
; Can be set to one these values:
|
||||
; "none" / "vizhash" / "identicon" / "jdenticon" (default).
|
||||
; "none" / "identicon" (default) / "jdenticon" / "vizhash".
|
||||
; icon = "none"
|
||||
|
||||
; Content Security Policy headers allow a website to restrict what sources are
|
||||
|
|
12
composer.lock
generated
12
composer.lock
generated
|
@ -370,16 +370,16 @@
|
|||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v4.15.1",
|
||||
"version": "v4.15.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900"
|
||||
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
|
||||
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
|
||||
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -420,9 +420,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
|
||||
},
|
||||
"time": "2022-09-04T07:30:47+00:00"
|
||||
"time": "2022-11-12T15:38:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
|
|
193
i18n/th.json
Normal file
193
i18n/th.json
Normal file
|
@ -0,0 +1,193 @@
|
|||
{
|
||||
"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 เป็น pastebin ออนไลน์แบบโอเพ่นซอร์สที่มีสไตล์แบบมินิมัลลิสท์ เซิร์ฟเวอร์ไม่สามารถรู้ได้ว่าข้อมูลโค้ดที่มาฝากนั้นเป็นข้อมูลอะไร โดยจะถูก %sเข้ารหัส/ถอดรหัส%s ด้วยกระบวนการ AES จำนวน 256 บิตผ่านเบราว์เซอร์",
|
||||
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "ข้อมูลเพิ่มเติม ดูได้ที่<a href=\"https://privatebin.info/\">หน้าโครงการ</a>",
|
||||
"Because ignorance is bliss": "ไม่รู้ไม่ชี้ดีที่สุด",
|
||||
"en": "th",
|
||||
"Paste does not exist, has expired or has been deleted.": "การฝากโค้ดไม่มีอยู่ อาจจะหมดอายุหรือถูกลบไปแล้ว",
|
||||
"%s requires php %s or above to work. Sorry.": "ขออภัย %s ต้องใช้ PHP %s ขึ้นไปจึงจะใช้งานได้",
|
||||
"%s requires configuration section [%s] to be present in configuration file.": "%s จำเป็นต้องตั้งค่าตัวแปร [%s] ในไฟล์กำหนดค่า",
|
||||
"Please wait %d seconds between each post.": [
|
||||
"กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที",
|
||||
"กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที",
|
||||
"กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที",
|
||||
"กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที"
|
||||
],
|
||||
"Paste is limited to %s of encrypted data.": "การฝากโค้ดแบบเข้ารหัส ขีดจำกัดสูงสุดคือ %s",
|
||||
"Invalid data.": "ข้อมูลไม่ถูกต้อง",
|
||||
"You are unlucky. Try again.": "วันนี้คุณดวงไม่เฮงเลย ลองใหม่อีกครั้งนะ",
|
||||
"Error saving comment. Sorry.": "ขออภัย เกิดข้อผิดพลาดในระหว่างบันทึกความคิดเห็น",
|
||||
"Error saving paste. Sorry.": "ขออภัย เกิดข้อผิดพลาดในระหว่างบันทึกการฝากโค้ด",
|
||||
"Invalid paste ID.": "ID การฝากโค้ดไม่ถูกต้อง",
|
||||
"Paste is not of burn-after-reading type.": "ข้อมูลการฝากโค้ดนี้ไม่ได้เป็นรูปแบบลบทันทีเมื่อเปิดอ่าน",
|
||||
"Wrong deletion token. Paste was not deleted.": "โทเค็นการลบไม่ถูกต้อง ข้อมูลการฝากโค้ดไม่ถูกลบ",
|
||||
"Paste was properly deleted.": "ข้อมูลการฝากโค้ดถูกลบออกเรียบร้อยแล้ว",
|
||||
"JavaScript is required for %s to work. Sorry for the inconvenience.": "จำเป็นต้องใช้ JavaScript เพื่อให้ %s สามารถทำงานได้ ขออภัยในความไม่สะดวก",
|
||||
"%s requires a modern browser to work.": "%s ต้องใช้เบราว์เซอร์สมัยใหม่ถึงจะสามารถใช้งานได้",
|
||||
"New": "ใหม่",
|
||||
"Send": "ส่ง",
|
||||
"Clone": "โคลน",
|
||||
"Raw text": "ข้อความล้วน",
|
||||
"Expires": "หมดอายุ",
|
||||
"Burn after reading": "ลบทันทีเมื่อเปิดอ่าน",
|
||||
"Open discussion": "แสดงความคิดเห็นได้",
|
||||
"Password (recommended)": "รหัสผ่าน (แนะนำให้ใส่)",
|
||||
"Discussion": "ความคิดเห็น",
|
||||
"Toggle navigation": "สลับเปิดปิดการนำทาง",
|
||||
"%d seconds": [
|
||||
"%d วินาที",
|
||||
"%d วินาที",
|
||||
"%d วินาที",
|
||||
"%d วินาที"
|
||||
],
|
||||
"%d minutes": [
|
||||
"%d นาที",
|
||||
"%d นาที",
|
||||
"%d นาที",
|
||||
"%d นาที"
|
||||
],
|
||||
"%d hours": [
|
||||
"%d ชั่วโมง",
|
||||
"%d ชั่วโมง",
|
||||
"%d ชั่วโมง",
|
||||
"%d ชั่วโมง"
|
||||
],
|
||||
"%d days": [
|
||||
"%d วัน",
|
||||
"%d วัน",
|
||||
"%d วัน",
|
||||
"%d วัน"
|
||||
],
|
||||
"%d weeks": [
|
||||
"%d สัปดาห์",
|
||||
"%d สัปดาห์",
|
||||
"%d สัปดาห์",
|
||||
"%d สัปดาห์"
|
||||
],
|
||||
"%d months": [
|
||||
"%d เดือน",
|
||||
"%d เดือน",
|
||||
"%d เดือน",
|
||||
"%d เดือน"
|
||||
],
|
||||
"%d years": [
|
||||
"%d ปี",
|
||||
"%d ปี",
|
||||
"%d ปี",
|
||||
"%d ปี"
|
||||
],
|
||||
"Never": "ไม่หมดอายุ",
|
||||
"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 วินาที",
|
||||
"เอกสารนี้จะหมดอายุใน %d วินาที",
|
||||
"เอกสารนี้จะหมดอายุใน %d วินาที"
|
||||
],
|
||||
"This document will expire in %d minutes.": [
|
||||
"เอกสารนี้จะหมดอายุใน %d นาที",
|
||||
"เอกสารนี้จะหมดอายุใน %d นาที",
|
||||
"เอกสารนี้จะหมดอายุใน %d นาที",
|
||||
"เอกสารนี้จะหมดอายุใน %d นาที"
|
||||
],
|
||||
"This document will expire in %d hours.": [
|
||||
"เอกสารนี้จะหมดอายุใน %d ชั่วโมง",
|
||||
"เอกสารนี้จะหมดอายุใน %d ชั่วโมง",
|
||||
"เอกสารนี้จะหมดอายุใน %d ชั่วโมง",
|
||||
"เอกสารนี้จะหมดอายุใน %d ชั่วโมง"
|
||||
],
|
||||
"This document will expire in %d days.": [
|
||||
"เอกสารนี้จะหมดอายุใน %d วัน",
|
||||
"เอกสารนี้จะหมดอายุใน %d วัน",
|
||||
"เอกสารนี้จะหมดอายุใน %d วัน",
|
||||
"เอกสารนี้จะหมดอายุใน %d วัน"
|
||||
],
|
||||
"This document will expire in %d months.": [
|
||||
"เอกสารนี้จะหมดอายุใน %d เดือน",
|
||||
"เอกสารนี้จะหมดอายุใน %d เดือน",
|
||||
"เอกสารนี้จะหมดอายุใน %d เดือน",
|
||||
"เอกสารนี้จะหมดอายุใน %d เดือน"
|
||||
],
|
||||
"Please enter the password for this paste:": "กรุณากรอกรหัสผ่านเพื่อเปิดข้อมูลการฝากโค้ดนี้:",
|
||||
"Could not decrypt data (Wrong key?)": "ไม่สามารถถอดรหัสข้อมูลได้ (คีย์ไม่ถูกต้องหรือไม่)",
|
||||
"Could not delete the paste, it was not stored in burn after reading mode.": "ไม่สามารถลบการฝากโค้ดนี้ได้ เนื่องจากว่าไม่ได้ถูกเก็บไว้ในโหมดลบทันทีเมื่อเปิดอ่าน",
|
||||
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "เก็บไว้ดูคนเดียวนะ อย่าปิดหน้าต่างนี้ ข้อความนี้จะไม่สามารถแสดงได้อีก",
|
||||
"Could not decrypt comment; Wrong key?": "ไม่สามารถถอดรหัสความคิดเห็นได้ คีย์ไม่ถูกต้องหรือไม่",
|
||||
"Reply": "ตอบกลับ",
|
||||
"Anonymous": "ไม่ระบุชื่อ",
|
||||
"Avatar generated from IP address": "อวาตารสร้างมาจากไอพี",
|
||||
"Add comment": "เพิ่มความคิดเห็น",
|
||||
"Optional nickname…": "ใส่ชื่อคนให้ความคิดเห็น…",
|
||||
"Post comment": "ส่งความคิดเห็น",
|
||||
"Sending comment…": "กำลังส่งความคิดเห็น…",
|
||||
"Comment posted.": "ส่งความคิดเห็นแล้ว",
|
||||
"Could not refresh display: %s": "ไม่สามารถรีเฟรชการแสดงผลได้: %s",
|
||||
"unknown status": "ไม่ทราบสถานะ",
|
||||
"server error or not responding": "เซิร์ฟเวอร์มีข้อผิดพลาดหรือไม่ตอบสนอง",
|
||||
"Could not post comment: %s": "ไม่สามารถส่งความคิดเห็นได้: %s",
|
||||
"Sending paste…": "กำลังส่งข้อมูล…",
|
||||
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "การฝากโค้ดของคุณอยู่ที่ <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(กดปุ่ม [Ctrl]+[c] เพื่อคัดลอก)</span>",
|
||||
"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 หายไป (คุณได้ใช้ตัวเปลี่ยนเส้นทางหรือตัวย่อ URL ที่มีการตัดส่วนของ URL ออกหรือไม่)",
|
||||
"B": "B",
|
||||
"KiB": "KiB",
|
||||
"MiB": "MiB",
|
||||
"GiB": "GiB",
|
||||
"TiB": "TiB",
|
||||
"PiB": "PiB",
|
||||
"EiB": "EiB",
|
||||
"ZiB": "ZiB",
|
||||
"YiB": "YiB",
|
||||
"Format": "รูปแบบ",
|
||||
"Plain Text": "ข้อความล้วน",
|
||||
"Source Code": "ซอร์สโค้ด",
|
||||
"Markdown": "Markdown",
|
||||
"Download attachment": "ดาวน์โหลดไฟล์แนบ",
|
||||
"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.": "ไฟล์มีขนาดใหญ่เกินไปที่จะแสดงตัวอย่าง กรุณาดาวน์โหลดเป็นไฟล์แนบแทน",
|
||||
"Remove attachment": "ลบไฟล์แนบ",
|
||||
"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\" กรุณาอัปเดต PATH ในไฟล์ index.php ของคุณ",
|
||||
"Decrypt": "ถอดรหัส",
|
||||
"Enter password": "กรอกรหัสผ่าน",
|
||||
"Loading…": "กำลังโหลด…",
|
||||
"Decrypting paste…": "กำลังถอดรหัสข้อมูลการฝากโค้ด…",
|
||||
"Preparing new paste…": "กำลังเตรียมข้อมูลการฝากโค้ดใหม่…",
|
||||
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "ในกรณีที่ข้อความนี้ยังปรากฎให้เห็นอยู่ กรุณาดู<a href=\"%s\">คำถามที่พบบ่อยนี้เพื่อใช้แก้ไขปัญหา</a>",
|
||||
"+++ no paste text +++": "+++ ไม่มีข้อความการฝากโค้ด +++",
|
||||
"Could not get paste data: %s": "ไม่สามารถดึงข้อมูลการฝากโค้ดได้: %s",
|
||||
"QR code": "คิวอาร์โค้ด",
|
||||
"This website is using an insecure HTTP connection! Please use it only for testing.": "เว็บไซต์นี้ใช้การเชื่อมต่อแบบ HTTP ที่ไม่ปลอดภัย! กรุณาใช้เพื่อการทดสอบเท่านั้น",
|
||||
"For more information <a href=\"%s\">see this FAQ entry</a>.": "สำหรับข้อมูลเพิ่มเติม <a href=\"%s\">กรุณาดูรายการคำถามที่พบบ่อยนี้</a>",
|
||||
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "เบราว์เซอร์ของคุณอาจต้องใช้การเชื่อมต่อ HTTPS เพื่อสนับสนุน API แบบ WebCrypto ลอง<a href=\"%s\">เปลี่ยนเป็น HTTPS</a>",
|
||||
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "เบราว์เซอร์ของคุณไม่สนับสนุน WebAssembly ที่ทำหน้าที่ในการบีบอัดข้อมูลในรูปแบบ zlib คุณยังสามารถสร้างเอกสารที่ไม่บีบอัด แต่จะไม่สามารถอ่านเอกสารที่บีบอัดได้",
|
||||
"waiting on user to provide a password": "กำลังรอผู้ใช้กรอกรหัสผ่าน",
|
||||
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "ไม่สามารถถอดรหัสข้อมูลได้ คุณกรอกรหัสผ่านผิดหรือเปล่า กดปุ่มลองอีกครั้งด้านบน",
|
||||
"Retry": "ลองอีกครั้ง",
|
||||
"Showing raw text…": "กำลังแสดงข้อความล้วน…",
|
||||
"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 หรือไม่",
|
||||
"Use Current Timezone": "ใช้โซนเวลาปัจจุบัน",
|
||||
"Convert To UTC": "แปลงเป็น UTC",
|
||||
"Close": "ปิด",
|
||||
"Encrypted note on %s": "เขารหัสบันทึกย่อบน %s",
|
||||
"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": "ดาวน์โหลดข้อมูลการฝากโค้ด",
|
||||
"Your IP is not authorized to create pastes.": "IP ของคุณไม่ได้รับอนุญาตให้สร้างการฝากโค้ด",
|
||||
"Trying to shorten a URL that isn't pointing at our instance.": "กำลังพยายามใช้เครื่องมือสร้างลิงก์ย่อ ที่ไม่ได้ชี้ไปที่อินสแตนซ์ของเรา",
|
||||
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "เกิดข้อผิดพลาดในการเรียก YOURLS อาจเป็นปัญหามาจากการกำหนดค่า เช่น \"apiurl\" หรือ \"signature\" ไม่ถูกต้องหรือขาดหายไป",
|
||||
"Error parsing YOURLS response.": "เกิดข้อผิดพลาดในการแยกวิเคราะห์การตอบสนองของ YOURLS"
|
||||
}
|
|
@ -10,14 +10,14 @@ global.fs = require('fs');
|
|||
global.WebCrypto = require('@peculiar/webcrypto').Crypto;
|
||||
|
||||
// application libraries to test
|
||||
global.$ = global.jQuery = require('./jquery-3.6.0');
|
||||
global.$ = global.jQuery = require('./jquery-3.6.1');
|
||||
global.RawDeflate = require('./rawinflate-0.3').RawDeflate;
|
||||
global.zlib = require('./zlib-1.2.13').zlib;
|
||||
require('./prettify');
|
||||
global.prettyPrint = window.PR.prettyPrint;
|
||||
global.prettyPrintOne = window.PR.prettyPrintOne;
|
||||
global.showdown = require('./showdown-2.0.3');
|
||||
global.DOMPurify = require('./purify-2.3.6');
|
||||
global.showdown = require('./showdown-2.1.0');
|
||||
global.DOMPurify = require('./purify-2.4.6');
|
||||
global.baseX = require('./base-x-4.0.0').baseX;
|
||||
global.Legacy = require('./legacy').Legacy;
|
||||
require('./bootstrap-3.4.1');
|
||||
|
|
2
js/jquery-3.6.0.js
vendored
2
js/jquery-3.6.0.js
vendored
File diff suppressed because one or more lines are too long
2
js/jquery-3.6.1.js
vendored
Normal file
2
js/jquery-3.6.1.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -627,7 +627,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
* @prop {string[]}
|
||||
* @readonly
|
||||
*/
|
||||
const supportedLanguages = ['bg', 'ca', 'co', 'cs', 'de', 'el', 'es', 'et', 'fi', 'fr', 'he', 'hu', 'id', 'it', 'jbo', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sk', 'sl', 'tr', 'uk', 'zh'];
|
||||
const supportedLanguages = ['bg', 'ca', 'co', 'cs', 'de', 'el', 'es', 'et', 'fi', 'fr', 'he', 'hu', 'id', 'it', 'jbo', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sk', 'sl', 'th', 'tr', 'uk', 'zh'];
|
||||
|
||||
/**
|
||||
* built in language
|
||||
|
@ -815,6 +815,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
|||
return n === 1 ? 0 : (n === 2 ? 1 : ((n < 0 || n > 10) && (n % 10 === 0) ? 2 : 3));
|
||||
case 'id':
|
||||
case 'jbo':
|
||||
case 'th':
|
||||
return 0;
|
||||
case 'lt':
|
||||
return n % 10 === 1 && n % 100 !== 11 ? 0 : ((n % 10 >= 2 && n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
|
||||
|
|
File diff suppressed because one or more lines are too long
2
js/purify-2.4.6.js
Normal file
2
js/purify-2.4.6.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -53,7 +53,7 @@ class Configuration
|
|||
'languagedefault' => '',
|
||||
'urlshortener' => '',
|
||||
'qrcode' => true,
|
||||
'icon' => 'jdenticon',
|
||||
'icon' => 'identicon',
|
||||
'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\'; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
|
||||
'zerobincompatibility' => false,
|
||||
'httpwarning' => true,
|
||||
|
|
|
@ -15,61 +15,17 @@ namespace PrivateBin\Data;
|
|||
/**
|
||||
* AbstractData
|
||||
*
|
||||
* Abstract model for data access, implemented as a singleton.
|
||||
* Abstract model for data access
|
||||
*/
|
||||
abstract class AbstractData
|
||||
{
|
||||
/**
|
||||
* Singleton instance
|
||||
*
|
||||
* @access protected
|
||||
* @static
|
||||
* @var AbstractData
|
||||
*/
|
||||
protected static $_instance = null;
|
||||
|
||||
/**
|
||||
* cache for the traffic limiter
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected static $_last_cache = array();
|
||||
|
||||
/**
|
||||
* Enforce singleton, disable constructor
|
||||
*
|
||||
* Instantiate using {@link getInstance()}, this object implements the singleton pattern.
|
||||
*
|
||||
* @access protected
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce singleton, disable cloning
|
||||
*
|
||||
* Instantiate using {@link getInstance()}, this object implements the singleton pattern.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function __clone()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance of singleton
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param array $options
|
||||
* @return AbstractData
|
||||
*/
|
||||
public static function getInstance(array $options)
|
||||
{
|
||||
}
|
||||
protected $_last_cache = array();
|
||||
|
||||
/**
|
||||
* Create a paste.
|
||||
|
@ -150,9 +106,9 @@ abstract class AbstractData
|
|||
public function purgeValues($namespace, $time)
|
||||
{
|
||||
if ($namespace === 'traffic_limiter') {
|
||||
foreach (self::$_last_cache as $key => $last_submission) {
|
||||
foreach ($this->_last_cache as $key => $last_submission) {
|
||||
if ($last_submission <= $time) {
|
||||
unset(self::$_last_cache[$key]);
|
||||
unset($this->_last_cache[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,6 +163,14 @@ abstract class AbstractData
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all paste ids
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getAllPastes();
|
||||
|
||||
/**
|
||||
* Get next free slot for comment from postdate.
|
||||
*
|
||||
|
|
|
@ -25,59 +25,43 @@ use PrivateBin\Json;
|
|||
*/
|
||||
class Database extends AbstractData
|
||||
{
|
||||
/**
|
||||
* cache for select queries
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $_cache = array();
|
||||
|
||||
/**
|
||||
* instance of database connection
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var PDO
|
||||
*/
|
||||
private static $_db;
|
||||
private $_db;
|
||||
|
||||
/**
|
||||
* table prefix
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
private static $_prefix = '';
|
||||
private $_prefix = '';
|
||||
|
||||
/**
|
||||
* database type
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
private static $_type = '';
|
||||
private $_type = '';
|
||||
|
||||
/**
|
||||
* get instance of singleton
|
||||
* instantiates a new Database data backend
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param array $options
|
||||
* @throws Exception
|
||||
* @return Database
|
||||
* @return
|
||||
*/
|
||||
public static function getInstance(array $options)
|
||||
public function __construct(array $options)
|
||||
{
|
||||
// if needed initialize the singleton
|
||||
if (!(self::$_instance instanceof self)) {
|
||||
self::$_instance = new self;
|
||||
}
|
||||
|
||||
// set table prefix if given
|
||||
if (array_key_exists('tbl', $options)) {
|
||||
self::$_prefix = $options['tbl'];
|
||||
$this->_prefix = $options['tbl'];
|
||||
}
|
||||
|
||||
// initialize the db connection with new options
|
||||
|
@ -94,16 +78,16 @@ class Database extends AbstractData
|
|||
$db_tables_exist = true;
|
||||
|
||||
// setup type and dabase connection
|
||||
self::$_type = strtolower(
|
||||
$this->_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')) {
|
||||
if ($this->_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
|
||||
$options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ANSI_QUOTES'";
|
||||
}
|
||||
$tableQuery = self::_getTableQuery(self::$_type);
|
||||
self::$_db = new PDO(
|
||||
$tableQuery = $this->_getTableQuery($this->_type);
|
||||
$this->_db = new PDO(
|
||||
$options['dsn'],
|
||||
$options['usr'],
|
||||
$options['pwd'],
|
||||
|
@ -111,43 +95,41 @@ class Database extends AbstractData
|
|||
);
|
||||
|
||||
// check if the database contains the required tables
|
||||
$tables = self::$_db->query($tableQuery)->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$tables = $this->_db->query($tableQuery)->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
|
||||
// create paste table if necessary
|
||||
if (!in_array(self::_sanitizeIdentifier('paste'), $tables)) {
|
||||
self::_createPasteTable();
|
||||
if (!in_array($this->_sanitizeIdentifier('paste'), $tables)) {
|
||||
$this->_createPasteTable();
|
||||
$db_tables_exist = false;
|
||||
}
|
||||
|
||||
// create comment table if necessary
|
||||
if (!in_array(self::_sanitizeIdentifier('comment'), $tables)) {
|
||||
self::_createCommentTable();
|
||||
if (!in_array($this->_sanitizeIdentifier('comment'), $tables)) {
|
||||
$this->_createCommentTable();
|
||||
$db_tables_exist = false;
|
||||
}
|
||||
|
||||
// create config table if necessary
|
||||
$db_version = Controller::VERSION;
|
||||
if (!in_array(self::_sanitizeIdentifier('config'), $tables)) {
|
||||
self::_createConfigTable();
|
||||
if (!in_array($this->_sanitizeIdentifier('config'), $tables)) {
|
||||
$this->_createConfigTable();
|
||||
// if we only needed to create the config table, the DB is older then 0.22
|
||||
if ($db_tables_exist) {
|
||||
$db_version = '0.21';
|
||||
}
|
||||
} else {
|
||||
$db_version = self::_getConfig('VERSION');
|
||||
$db_version = $this->_getConfig('VERSION');
|
||||
}
|
||||
|
||||
// update database structure if necessary
|
||||
if (version_compare($db_version, Controller::VERSION, '<')) {
|
||||
self::_upgradeDatabase($db_version);
|
||||
$this->_upgradeDatabase($db_version);
|
||||
}
|
||||
} else {
|
||||
throw new Exception(
|
||||
'Missing configuration for key dsn, usr, pwd or opt in the section model_options, please check your configuration file', 6
|
||||
);
|
||||
}
|
||||
|
||||
return self::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -160,22 +142,12 @@ class Database extends AbstractData
|
|||
*/
|
||||
public function create($pasteid, array $paste)
|
||||
{
|
||||
if (
|
||||
array_key_exists($pasteid, self::$_cache)
|
||||
) {
|
||||
if (false !== self::$_cache[$pasteid]) {
|
||||
return false;
|
||||
} else {
|
||||
unset(self::$_cache[$pasteid]);
|
||||
}
|
||||
}
|
||||
|
||||
$expire_date = 0;
|
||||
$opendiscussion = $burnafterreading = false;
|
||||
$attachment = $attachmentname = null;
|
||||
$meta = $paste['meta'];
|
||||
$isVersion1 = array_key_exists('data', $paste);
|
||||
list($createdKey) = self::_getVersionedKeys($isVersion1 ? 1 : 2);
|
||||
list($createdKey) = $this->_getVersionedKeys($isVersion1 ? 1 : 2);
|
||||
$created = (int) $meta[$createdKey];
|
||||
unset($meta[$createdKey], $paste['meta']);
|
||||
if (array_key_exists('expire_date', $meta)) {
|
||||
|
@ -204,8 +176,8 @@ class Database extends AbstractData
|
|||
$burnafterreading = $paste['adata'][3];
|
||||
}
|
||||
try {
|
||||
return self::_exec(
|
||||
'INSERT INTO "' . self::_sanitizeIdentifier('paste') .
|
||||
return $this->_exec(
|
||||
'INSERT INTO "' . $this->_sanitizeIdentifier('paste') .
|
||||
'" VALUES(?,?,?,?,?,?,?,?,?)',
|
||||
array(
|
||||
$pasteid,
|
||||
|
@ -233,64 +205,59 @@ class Database extends AbstractData
|
|||
*/
|
||||
public function read($pasteid)
|
||||
{
|
||||
if (array_key_exists($pasteid, self::$_cache)) {
|
||||
return self::$_cache[$pasteid];
|
||||
}
|
||||
|
||||
self::$_cache[$pasteid] = false;
|
||||
try {
|
||||
$paste = self::_select(
|
||||
'SELECT * FROM "' . self::_sanitizeIdentifier('paste') .
|
||||
$row = $this->_select(
|
||||
'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') .
|
||||
'" WHERE "dataid" = ?', array($pasteid), true
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$paste = false;
|
||||
$row = false;
|
||||
}
|
||||
if ($paste === false) {
|
||||
if ($row === false) {
|
||||
return false;
|
||||
}
|
||||
// create array
|
||||
$data = Json::decode($paste['data']);
|
||||
$data = Json::decode($row['data']);
|
||||
$isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2;
|
||||
if ($isVersion2) {
|
||||
self::$_cache[$pasteid] = $data;
|
||||
list($createdKey) = self::_getVersionedKeys(2);
|
||||
$paste = $data;
|
||||
list($createdKey) = $this->_getVersionedKeys(2);
|
||||
} else {
|
||||
self::$_cache[$pasteid] = array('data' => $paste['data']);
|
||||
list($createdKey) = self::_getVersionedKeys(1);
|
||||
$paste = array('data' => $row['data']);
|
||||
list($createdKey) = $this->_getVersionedKeys(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$paste['meta'] = Json::decode($paste['meta']);
|
||||
$row['meta'] = Json::decode($row['meta']);
|
||||
} catch (Exception $e) {
|
||||
$paste['meta'] = array();
|
||||
$row['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'];
|
||||
$row = self::upgradePreV1Format($row);
|
||||
$paste['meta'] = $row['meta'];
|
||||
$paste['meta'][$createdKey] = (int) $row['postdate'];
|
||||
$expire_date = (int) $row['expiredate'];
|
||||
if ($expire_date > 0) {
|
||||
self::$_cache[$pasteid]['meta']['expire_date'] = $expire_date;
|
||||
$paste['meta']['expire_date'] = $expire_date;
|
||||
}
|
||||
if ($isVersion2) {
|
||||
return self::$_cache[$pasteid];
|
||||
return $paste;
|
||||
}
|
||||
|
||||
// support v1 attachments
|
||||
if (array_key_exists('attachment', $paste) && !empty($paste['attachment'])) {
|
||||
self::$_cache[$pasteid]['attachment'] = $paste['attachment'];
|
||||
if (array_key_exists('attachmentname', $paste) && !empty($paste['attachmentname'])) {
|
||||
self::$_cache[$pasteid]['attachmentname'] = $paste['attachmentname'];
|
||||
if (array_key_exists('attachment', $row) && !empty($row['attachment'])) {
|
||||
$paste['attachment'] = $row['attachment'];
|
||||
if (array_key_exists('attachmentname', $row) && !empty($row['attachmentname'])) {
|
||||
$paste['attachmentname'] = $row['attachmentname'];
|
||||
}
|
||||
}
|
||||
if ($paste['opendiscussion']) {
|
||||
self::$_cache[$pasteid]['meta']['opendiscussion'] = true;
|
||||
if ($row['opendiscussion']) {
|
||||
$paste['meta']['opendiscussion'] = true;
|
||||
}
|
||||
if ($paste['burnafterreading']) {
|
||||
self::$_cache[$pasteid]['meta']['burnafterreading'] = true;
|
||||
if ($row['burnafterreading']) {
|
||||
$paste['meta']['burnafterreading'] = true;
|
||||
}
|
||||
|
||||
return self::$_cache[$pasteid];
|
||||
return $paste;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -301,19 +268,14 @@ class Database extends AbstractData
|
|||
*/
|
||||
public function delete($pasteid)
|
||||
{
|
||||
self::_exec(
|
||||
'DELETE FROM "' . self::_sanitizeIdentifier('paste') .
|
||||
$this->_exec(
|
||||
'DELETE FROM "' . $this->_sanitizeIdentifier('paste') .
|
||||
'" WHERE "dataid" = ?', array($pasteid)
|
||||
);
|
||||
self::_exec(
|
||||
'DELETE FROM "' . self::_sanitizeIdentifier('comment') .
|
||||
$this->_exec(
|
||||
'DELETE FROM "' . $this->_sanitizeIdentifier('comment') .
|
||||
'" WHERE "pasteid" = ?', array($pasteid)
|
||||
);
|
||||
if (
|
||||
array_key_exists($pasteid, self::$_cache)
|
||||
) {
|
||||
unset(self::$_cache[$pasteid]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -325,12 +287,15 @@ class Database extends AbstractData
|
|||
*/
|
||||
public function exists($pasteid)
|
||||
{
|
||||
if (
|
||||
!array_key_exists($pasteid, self::$_cache)
|
||||
) {
|
||||
self::$_cache[$pasteid] = $this->read($pasteid);
|
||||
try {
|
||||
$row = $this->_select(
|
||||
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') .
|
||||
'" WHERE "dataid" = ?', array($pasteid), true
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
return (bool) self::$_cache[$pasteid];
|
||||
return (bool) $row;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -352,7 +317,7 @@ class Database extends AbstractData
|
|||
$version = 2;
|
||||
$data = Json::encode($comment);
|
||||
}
|
||||
list($createdKey, $iconKey) = self::_getVersionedKeys($version);
|
||||
list($createdKey, $iconKey) = $this->_getVersionedKeys($version);
|
||||
$meta = $comment['meta'];
|
||||
unset($comment['meta']);
|
||||
foreach (array('nickname', $iconKey) as $key) {
|
||||
|
@ -361,8 +326,8 @@ class Database extends AbstractData
|
|||
}
|
||||
}
|
||||
try {
|
||||
return self::_exec(
|
||||
'INSERT INTO "' . self::_sanitizeIdentifier('comment') .
|
||||
return $this->_exec(
|
||||
'INSERT INTO "' . $this->_sanitizeIdentifier('comment') .
|
||||
'" VALUES(?,?,?,?,?,?,?)',
|
||||
array(
|
||||
$commentid,
|
||||
|
@ -388,8 +353,8 @@ class Database extends AbstractData
|
|||
*/
|
||||
public function readComments($pasteid)
|
||||
{
|
||||
$rows = self::_select(
|
||||
'SELECT * FROM "' . self::_sanitizeIdentifier('comment') .
|
||||
$rows = $this->_select(
|
||||
'SELECT * FROM "' . $this->_sanitizeIdentifier('comment') .
|
||||
'" WHERE "pasteid" = ?', array($pasteid)
|
||||
);
|
||||
|
||||
|
@ -406,7 +371,7 @@ class Database extends AbstractData
|
|||
$version = 1;
|
||||
$comments[$i] = array('data' => $row['data']);
|
||||
}
|
||||
list($createdKey, $iconKey) = self::_getVersionedKeys($version);
|
||||
list($createdKey, $iconKey) = $this->_getVersionedKeys($version);
|
||||
$comments[$i]['id'] = $row['dataid'];
|
||||
$comments[$i]['parentid'] = $row['parentid'];
|
||||
$comments[$i]['meta'] = array($createdKey => (int) $row['postdate']);
|
||||
|
@ -433,8 +398,8 @@ class Database extends AbstractData
|
|||
public function existsComment($pasteid, $parentid, $commentid)
|
||||
{
|
||||
try {
|
||||
return (bool) self::_select(
|
||||
'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('comment') .
|
||||
return (bool) $this->_select(
|
||||
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('comment') .
|
||||
'" WHERE "pasteid" = ? AND "parentid" = ? AND "dataid" = ?',
|
||||
array($pasteid, $parentid, $commentid), true
|
||||
);
|
||||
|
@ -455,15 +420,15 @@ class Database extends AbstractData
|
|||
public function setValue($value, $namespace, $key = '')
|
||||
{
|
||||
if ($namespace === 'traffic_limiter') {
|
||||
self::$_last_cache[$key] = $value;
|
||||
$this->_last_cache[$key] = $value;
|
||||
try {
|
||||
$value = Json::encode(self::$_last_cache);
|
||||
$value = Json::encode($this->_last_cache);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return self::_exec(
|
||||
'UPDATE "' . self::_sanitizeIdentifier('config') .
|
||||
return $this->_exec(
|
||||
'UPDATE "' . $this->_sanitizeIdentifier('config') .
|
||||
'" SET "value" = ? WHERE "id" = ?',
|
||||
array($value, strtoupper($namespace))
|
||||
);
|
||||
|
@ -483,8 +448,8 @@ class Database extends AbstractData
|
|||
$value = $this->_getConfig($configKey);
|
||||
if ($value === '') {
|
||||
// initialize the row, so that setValue can rely on UPDATE queries
|
||||
self::_exec(
|
||||
'INSERT INTO "' . self::_sanitizeIdentifier('config') .
|
||||
$this->_exec(
|
||||
'INSERT INTO "' . $this->_sanitizeIdentifier('config') .
|
||||
'" VALUES(?,?)',
|
||||
array($configKey, '')
|
||||
);
|
||||
|
@ -492,7 +457,8 @@ class Database extends AbstractData
|
|||
// 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');
|
||||
$fs = new Filesystem(array('dir' => 'data'));
|
||||
$value = $fs->getValue('salt');
|
||||
$this->setValue($value, 'salt');
|
||||
@unlink($file);
|
||||
return $value;
|
||||
|
@ -500,12 +466,12 @@ class Database extends AbstractData
|
|||
}
|
||||
if ($value && $namespace === 'traffic_limiter') {
|
||||
try {
|
||||
self::$_last_cache = Json::decode($value);
|
||||
$this->_last_cache = Json::decode($value);
|
||||
} catch (Exception $e) {
|
||||
self::$_last_cache = array();
|
||||
$this->_last_cache = array();
|
||||
}
|
||||
if (array_key_exists($key, self::$_last_cache)) {
|
||||
return self::$_last_cache[$key];
|
||||
if (array_key_exists($key, $this->_last_cache)) {
|
||||
return $this->_last_cache[$key];
|
||||
}
|
||||
}
|
||||
return (string) $value;
|
||||
|
@ -520,34 +486,37 @@ class Database extends AbstractData
|
|||
*/
|
||||
protected function _getExpiredPastes($batchsize)
|
||||
{
|
||||
$pastes = array();
|
||||
$rows = self::_select(
|
||||
'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('paste') .
|
||||
$statement = $this->_db->prepare(
|
||||
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') .
|
||||
'" WHERE "expiredate" < ? AND "expiredate" != ? ' .
|
||||
(self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'),
|
||||
array(time(), 0, $batchsize)
|
||||
($this->_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?')
|
||||
);
|
||||
if (is_array($rows) && count($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
$pastes[] = $row['dataid'];
|
||||
$statement->execute(array(time(), 0, $batchsize));
|
||||
return $statement->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
}
|
||||
}
|
||||
return $pastes;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getAllPastes()
|
||||
{
|
||||
return $this->_db->query(
|
||||
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"'
|
||||
)->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* execute a statement
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @throws PDOException
|
||||
* @return bool
|
||||
*/
|
||||
private static function _exec($sql, array $params)
|
||||
private function _exec($sql, array $params)
|
||||
{
|
||||
$statement = self::$_db->prepare($sql);
|
||||
$statement = $this->_db->prepare($sql);
|
||||
foreach ($params as $key => &$parameter) {
|
||||
$position = $key + 1;
|
||||
if (is_int($parameter)) {
|
||||
|
@ -567,20 +536,19 @@ class Database extends AbstractData
|
|||
* run a select statement
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @param bool $firstOnly if only the first row should be returned
|
||||
* @throws PDOException
|
||||
* @return array|false
|
||||
*/
|
||||
private static function _select($sql, array $params, $firstOnly = false)
|
||||
private function _select($sql, array $params, $firstOnly = false)
|
||||
{
|
||||
$statement = self::$_db->prepare($sql);
|
||||
$statement = $this->_db->prepare($sql);
|
||||
$statement->execute($params);
|
||||
if ($firstOnly) {
|
||||
$result = $statement->fetch(PDO::FETCH_ASSOC);
|
||||
} elseif (self::$_type === 'oci') {
|
||||
} elseif ($this->_type === 'oci') {
|
||||
// workaround for https://bugs.php.net/bug.php?id=46728
|
||||
$result = array();
|
||||
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
@ -590,7 +558,7 @@ class Database extends AbstractData
|
|||
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
$statement->closeCursor();
|
||||
if (self::$_type === 'oci' && is_array($result)) {
|
||||
if ($this->_type === 'oci' && is_array($result)) {
|
||||
// returned CLOB values are streams, convert these into strings
|
||||
$result = $firstOnly ?
|
||||
array_map('PrivateBin\Data\Database::_sanitizeClob', $result) :
|
||||
|
@ -603,11 +571,10 @@ class Database extends AbstractData
|
|||
* get version dependent key names
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param int $version
|
||||
* @return array
|
||||
*/
|
||||
private static function _getVersionedKeys($version)
|
||||
private function _getVersionedKeys($version)
|
||||
{
|
||||
if ($version === 1) {
|
||||
return array('postdate', 'vizhash');
|
||||
|
@ -619,12 +586,11 @@ class Database extends AbstractData
|
|||
* get table list query, depending on the database type
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $type
|
||||
* @throws Exception
|
||||
* @return string
|
||||
*/
|
||||
private static function _getTableQuery($type)
|
||||
private function _getTableQuery($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'ibm':
|
||||
|
@ -675,15 +641,14 @@ class Database extends AbstractData
|
|||
* get a value by key from the config table
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
private static function _getConfig($key)
|
||||
private function _getConfig($key)
|
||||
{
|
||||
try {
|
||||
$row = self::_select(
|
||||
'SELECT "value" FROM "' . self::_sanitizeIdentifier('config') .
|
||||
$row = $this->_select(
|
||||
'SELECT "value" FROM "' . $this->_sanitizeIdentifier('config') .
|
||||
'" WHERE "id" = ?', array($key), true
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
|
@ -696,14 +661,13 @@ class Database extends AbstractData
|
|||
* get the primary key clauses, depending on the database driver
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
private static function _getPrimaryKeyClauses($key = 'dataid')
|
||||
private function _getPrimaryKeyClauses($key = 'dataid')
|
||||
{
|
||||
$main_key = $after_key = '';
|
||||
switch (self::$_type) {
|
||||
switch ($this->_type) {
|
||||
case 'mysql':
|
||||
case 'oci':
|
||||
$after_key = ", PRIMARY KEY (\"$key\")";
|
||||
|
@ -721,12 +685,11 @@ class Database extends AbstractData
|
|||
* PostgreSQL and OCI uses a different API for BLOBs then SQL, hence we use TEXT and CLOB
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @return string
|
||||
*/
|
||||
private static function _getDataType()
|
||||
private function _getDataType()
|
||||
{
|
||||
switch (self::$_type) {
|
||||
switch ($this->_type) {
|
||||
case 'oci':
|
||||
return 'CLOB';
|
||||
case 'pgsql':
|
||||
|
@ -742,12 +705,11 @@ class Database extends AbstractData
|
|||
* PostgreSQL and OCI use different APIs for BLOBs then SQL, hence we use TEXT and CLOB
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @return string
|
||||
*/
|
||||
private static function _getAttachmentType()
|
||||
private function _getAttachmentType()
|
||||
{
|
||||
switch (self::$_type) {
|
||||
switch ($this->_type) {
|
||||
case 'oci':
|
||||
return 'CLOB';
|
||||
case 'pgsql':
|
||||
|
@ -763,12 +725,11 @@ class Database extends AbstractData
|
|||
* OCI doesn't accept TEXT so it has to be VARCHAR2(4000)
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @return string
|
||||
*/
|
||||
private static function _getMetaType()
|
||||
private function _getMetaType()
|
||||
{
|
||||
switch (self::$_type) {
|
||||
switch ($this->_type) {
|
||||
case 'oci':
|
||||
return 'VARCHAR2(4000)';
|
||||
default:
|
||||
|
@ -780,16 +741,15 @@ class Database extends AbstractData
|
|||
* create the paste table
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*/
|
||||
private static function _createPasteTable()
|
||||
private function _createPasteTable()
|
||||
{
|
||||
list($main_key, $after_key) = self::_getPrimaryKeyClauses();
|
||||
$dataType = self::_getDataType();
|
||||
$attachmentType = self::_getAttachmentType();
|
||||
$metaType = self::_getMetaType();
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE "' . self::_sanitizeIdentifier('paste') . '" ( ' .
|
||||
list($main_key, $after_key) = $this->_getPrimaryKeyClauses();
|
||||
$dataType = $this->_getDataType();
|
||||
$attachmentType = $this->_getAttachmentType();
|
||||
$metaType = $this->_getMetaType();
|
||||
$this->_db->exec(
|
||||
'CREATE TABLE "' . $this->_sanitizeIdentifier('paste') . '" ( ' .
|
||||
"\"dataid\" CHAR(16) NOT NULL$main_key, " .
|
||||
"\"data\" $attachmentType, " .
|
||||
'"postdate" INT, ' .
|
||||
|
@ -806,14 +766,13 @@ class Database extends AbstractData
|
|||
* create the paste table
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*/
|
||||
private static function _createCommentTable()
|
||||
private function _createCommentTable()
|
||||
{
|
||||
list($main_key, $after_key) = self::_getPrimaryKeyClauses();
|
||||
$dataType = self::_getDataType();
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE "' . self::_sanitizeIdentifier('comment') . '" ( ' .
|
||||
list($main_key, $after_key) = $this->_getPrimaryKeyClauses();
|
||||
$dataType = $this->_getDataType();
|
||||
$this->_db->exec(
|
||||
'CREATE TABLE "' . $this->_sanitizeIdentifier('comment') . '" ( ' .
|
||||
"\"dataid\" CHAR(16) NOT NULL$main_key, " .
|
||||
'"pasteid" CHAR(16), ' .
|
||||
'"parentid" CHAR(16), ' .
|
||||
|
@ -822,15 +781,15 @@ class Database extends AbstractData
|
|||
"\"vizhash\" $dataType, " .
|
||||
"\"postdate\" INT$after_key )"
|
||||
);
|
||||
if (self::$_type === 'oci') {
|
||||
self::$_db->exec(
|
||||
if ($this->_type === 'oci') {
|
||||
$this->_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")\';
|
||||
execute immediate \'create index "comment_parent" on "' . $this->_sanitizeIdentifier('comment') . '" ("pasteid")\';
|
||||
exception
|
||||
when already_exists or columns_indexed then
|
||||
NULL;
|
||||
|
@ -838,10 +797,10 @@ class Database extends AbstractData
|
|||
);
|
||||
} else {
|
||||
// CREATE INDEX IF NOT EXISTS not supported as of Oracle MySQL <= 8.0
|
||||
self::$_db->exec(
|
||||
$this->_db->exec(
|
||||
'CREATE INDEX "' .
|
||||
self::_sanitizeIdentifier('comment_parent') . '" ON "' .
|
||||
self::_sanitizeIdentifier('comment') . '" ("pasteid")'
|
||||
$this->_sanitizeIdentifier('comment_parent') . '" ON "' .
|
||||
$this->_sanitizeIdentifier('comment') . '" ("pasteid")'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -850,19 +809,18 @@ class Database extends AbstractData
|
|||
* create the paste table
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*/
|
||||
private static function _createConfigTable()
|
||||
private function _createConfigTable()
|
||||
{
|
||||
list($main_key, $after_key) = self::_getPrimaryKeyClauses('id');
|
||||
$charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)';
|
||||
$textType = self::_getMetaType();
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE "' . self::_sanitizeIdentifier('config') .
|
||||
list($main_key, $after_key) = $this->_getPrimaryKeyClauses('id');
|
||||
$charType = $this->_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)';
|
||||
$textType = $this->_getMetaType();
|
||||
$this->_db->exec(
|
||||
'CREATE TABLE "' . $this->_sanitizeIdentifier('config') .
|
||||
"\" ( \"id\" $charType NOT NULL$main_key, \"value\" $textType$after_key )"
|
||||
);
|
||||
self::_exec(
|
||||
'INSERT INTO "' . self::_sanitizeIdentifier('config') .
|
||||
$this->_exec(
|
||||
'INSERT INTO "' . $this->_sanitizeIdentifier('config') .
|
||||
'" VALUES(?,?)',
|
||||
array('VERSION', Controller::VERSION)
|
||||
);
|
||||
|
@ -890,90 +848,88 @@ class Database extends AbstractData
|
|||
* sanitizes identifiers
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $identifier
|
||||
* @return string
|
||||
*/
|
||||
private static function _sanitizeIdentifier($identifier)
|
||||
private function _sanitizeIdentifier($identifier)
|
||||
{
|
||||
return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier);
|
||||
return preg_replace('/[^A-Za-z0-9_]+/', '', $this->_prefix . $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* upgrade the database schema from an old version
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $oldversion
|
||||
*/
|
||||
private static function _upgradeDatabase($oldversion)
|
||||
private function _upgradeDatabase($oldversion)
|
||||
{
|
||||
$dataType = self::_getDataType();
|
||||
$attachmentType = self::_getAttachmentType();
|
||||
$dataType = $this->_getDataType();
|
||||
$attachmentType = $this->_getAttachmentType();
|
||||
switch ($oldversion) {
|
||||
case '0.21':
|
||||
// create the meta column if necessary (pre 0.21 change)
|
||||
try {
|
||||
self::$_db->exec(
|
||||
'SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" ' .
|
||||
(self::$_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1')
|
||||
$this->_db->exec(
|
||||
'SELECT "meta" FROM "' . $this->_sanitizeIdentifier('paste') . '" ' .
|
||||
($this->_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1')
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
self::$_db->exec('ALTER TABLE "' . self::_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT');
|
||||
$this->_db->exec('ALTER TABLE "' . $this->_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT');
|
||||
}
|
||||
// SQLite only allows one ALTER statement at a time...
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
|
||||
$this->_db->exec(
|
||||
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
|
||||
"\" ADD COLUMN \"attachment\" $attachmentType"
|
||||
);
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . "\" ADD COLUMN \"attachmentname\" $dataType"
|
||||
$this->_db->exec(
|
||||
'ALTER TABLE "' . $this->_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') .
|
||||
if ($this->_type !== 'sqlite') {
|
||||
$this->_db->exec(
|
||||
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
|
||||
"\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType"
|
||||
);
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE "' . self::_sanitizeIdentifier('comment') .
|
||||
$this->_db->exec(
|
||||
'ALTER TABLE "' . $this->_sanitizeIdentifier('comment') .
|
||||
"\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType, " .
|
||||
"MODIFY COLUMN \"nickname\" $dataType, MODIFY COLUMN \"vizhash\" $dataType"
|
||||
);
|
||||
} else {
|
||||
self::$_db->exec(
|
||||
$this->_db->exec(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "' .
|
||||
self::_sanitizeIdentifier('paste_dataid') . '" ON "' .
|
||||
self::_sanitizeIdentifier('paste') . '" ("dataid")'
|
||||
$this->_sanitizeIdentifier('paste_dataid') . '" ON "' .
|
||||
$this->_sanitizeIdentifier('paste') . '" ("dataid")'
|
||||
);
|
||||
self::$_db->exec(
|
||||
$this->_db->exec(
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "' .
|
||||
self::_sanitizeIdentifier('comment_dataid') . '" ON "' .
|
||||
self::_sanitizeIdentifier('comment') . '" ("dataid")'
|
||||
$this->_sanitizeIdentifier('comment_dataid') . '" ON "' .
|
||||
$this->_sanitizeIdentifier('comment') . '" ("dataid")'
|
||||
);
|
||||
}
|
||||
// CREATE INDEX IF NOT EXISTS not supported as of Oracle MySQL <= 8.0
|
||||
self::$_db->exec(
|
||||
$this->_db->exec(
|
||||
'CREATE INDEX "' .
|
||||
self::_sanitizeIdentifier('comment_parent') . '" ON "' .
|
||||
self::_sanitizeIdentifier('comment') . '" ("pasteid")'
|
||||
$this->_sanitizeIdentifier('comment_parent') . '" ON "' .
|
||||
$this->_sanitizeIdentifier('comment') . '" ("pasteid")'
|
||||
);
|
||||
// no break, continue with updates for 0.22 and later
|
||||
case '1.3':
|
||||
// SQLite doesn't support MODIFY, but it allows TEXT of similar
|
||||
// size as BLOB and PostgreSQL uses TEXT, so there is no need
|
||||
// to change it there
|
||||
if (self::$_type !== 'sqlite' && self::$_type !== 'pgsql') {
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
|
||||
if ($this->_type !== 'sqlite' && $this->_type !== 'pgsql') {
|
||||
$this->_db->exec(
|
||||
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
|
||||
"\" MODIFY COLUMN \"data\" $attachmentType"
|
||||
);
|
||||
}
|
||||
// no break, continue with updates for all newer versions
|
||||
default:
|
||||
self::_exec(
|
||||
'UPDATE "' . self::_sanitizeIdentifier('config') .
|
||||
$this->_exec(
|
||||
'UPDATE "' . $this->_sanitizeIdentifier('config') .
|
||||
'" SET "value" = ? WHERE "id" = ?',
|
||||
array(Controller::VERSION, 'VERSION')
|
||||
);
|
||||
|
|
|
@ -40,33 +40,26 @@ class Filesystem extends AbstractData
|
|||
* path in which to persist something
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
private static $_path = 'data';
|
||||
private $_path = 'data';
|
||||
|
||||
/**
|
||||
* get instance of singleton
|
||||
* instantiates a new Filesystem data backend
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param array $options
|
||||
* @return Filesystem
|
||||
* @return
|
||||
*/
|
||||
public static function getInstance(array $options)
|
||||
public function __construct(array $options)
|
||||
{
|
||||
// if needed initialize the singleton
|
||||
if (!(self::$_instance instanceof self)) {
|
||||
self::$_instance = new self;
|
||||
}
|
||||
// if given update the data directory
|
||||
if (
|
||||
is_array($options) &&
|
||||
array_key_exists('dir', $options)
|
||||
) {
|
||||
self::$_path = $options['dir'];
|
||||
$this->_path = $options['dir'];
|
||||
}
|
||||
return self::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,7 +72,7 @@ class Filesystem extends AbstractData
|
|||
*/
|
||||
public function create($pasteid, array $paste)
|
||||
{
|
||||
$storagedir = self::_dataid2path($pasteid);
|
||||
$storagedir = $this->_dataid2path($pasteid);
|
||||
$file = $storagedir . $pasteid . '.php';
|
||||
if (is_file($file)) {
|
||||
return false;
|
||||
|
@ -87,7 +80,7 @@ class Filesystem extends AbstractData
|
|||
if (!is_dir($storagedir)) {
|
||||
mkdir($storagedir, 0700, true);
|
||||
}
|
||||
return self::_store($file, $paste);
|
||||
return $this->_store($file, $paste);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,7 +94,7 @@ class Filesystem extends AbstractData
|
|||
{
|
||||
if (
|
||||
!$this->exists($pasteid) ||
|
||||
!$paste = self::_get(self::_dataid2path($pasteid) . $pasteid . '.php')
|
||||
!$paste = $this->_get($this->_dataid2path($pasteid) . $pasteid . '.php')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -116,7 +109,7 @@ class Filesystem extends AbstractData
|
|||
*/
|
||||
public function delete($pasteid)
|
||||
{
|
||||
$pastedir = self::_dataid2path($pasteid);
|
||||
$pastedir = $this->_dataid2path($pasteid);
|
||||
if (is_dir($pastedir)) {
|
||||
// Delete the paste itself.
|
||||
if (is_file($pastedir . $pasteid . '.php')) {
|
||||
|
@ -124,7 +117,7 @@ class Filesystem extends AbstractData
|
|||
}
|
||||
|
||||
// Delete discussion if it exists.
|
||||
$discdir = self::_dataid2discussionpath($pasteid);
|
||||
$discdir = $this->_dataid2discussionpath($pasteid);
|
||||
if (is_dir($discdir)) {
|
||||
// Delete all files in discussion directory
|
||||
$dir = dir($discdir);
|
||||
|
@ -148,20 +141,20 @@ class Filesystem extends AbstractData
|
|||
*/
|
||||
public function exists($pasteid)
|
||||
{
|
||||
$basePath = self::_dataid2path($pasteid) . $pasteid;
|
||||
$basePath = $this->_dataid2path($pasteid) . $pasteid;
|
||||
$pastePath = $basePath . '.php';
|
||||
// convert to PHP protected files if needed
|
||||
if (is_readable($basePath)) {
|
||||
self::_prependRename($basePath, $pastePath);
|
||||
$this->_prependRename($basePath, $pastePath);
|
||||
|
||||
// convert comments, too
|
||||
$discdir = self::_dataid2discussionpath($pasteid);
|
||||
$discdir = $this->_dataid2discussionpath($pasteid);
|
||||
if (is_dir($discdir)) {
|
||||
$dir = dir($discdir);
|
||||
while (false !== ($filename = $dir->read())) {
|
||||
if (substr($filename, -4) !== '.php' && strlen($filename) >= 16) {
|
||||
$commentFilename = $discdir . $filename . '.php';
|
||||
self::_prependRename($discdir . $filename, $commentFilename);
|
||||
$this->_prependRename($discdir . $filename, $commentFilename);
|
||||
}
|
||||
}
|
||||
$dir->close();
|
||||
|
@ -182,7 +175,7 @@ class Filesystem extends AbstractData
|
|||
*/
|
||||
public function createComment($pasteid, $parentid, $commentid, array $comment)
|
||||
{
|
||||
$storagedir = self::_dataid2discussionpath($pasteid);
|
||||
$storagedir = $this->_dataid2discussionpath($pasteid);
|
||||
$file = $storagedir . $pasteid . '.' . $commentid . '.' . $parentid . '.php';
|
||||
if (is_file($file)) {
|
||||
return false;
|
||||
|
@ -190,7 +183,7 @@ class Filesystem extends AbstractData
|
|||
if (!is_dir($storagedir)) {
|
||||
mkdir($storagedir, 0700, true);
|
||||
}
|
||||
return self::_store($file, $comment);
|
||||
return $this->_store($file, $comment);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -203,7 +196,7 @@ class Filesystem extends AbstractData
|
|||
public function readComments($pasteid)
|
||||
{
|
||||
$comments = array();
|
||||
$discdir = self::_dataid2discussionpath($pasteid);
|
||||
$discdir = $this->_dataid2discussionpath($pasteid);
|
||||
if (is_dir($discdir)) {
|
||||
$dir = dir($discdir);
|
||||
while (false !== ($filename = $dir->read())) {
|
||||
|
@ -212,7 +205,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 = self::_get($discdir . $filename);
|
||||
$comment = $this->_get($discdir . $filename);
|
||||
$items = explode('.', $filename);
|
||||
// Add some meta information not contained in file.
|
||||
$comment['id'] = $items[1];
|
||||
|
@ -243,7 +236,7 @@ class Filesystem extends AbstractData
|
|||
public function existsComment($pasteid, $parentid, $commentid)
|
||||
{
|
||||
return is_file(
|
||||
self::_dataid2discussionpath($pasteid) .
|
||||
$this->_dataid2discussionpath($pasteid) .
|
||||
$pasteid . '.' . $commentid . '.' . $parentid . '.php'
|
||||
);
|
||||
}
|
||||
|
@ -261,20 +254,20 @@ class Filesystem extends AbstractData
|
|||
{
|
||||
switch ($namespace) {
|
||||
case 'purge_limiter':
|
||||
return self::_storeString(
|
||||
self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php',
|
||||
return $this->_storeString(
|
||||
$this->_path . DIRECTORY_SEPARATOR . 'purge_limiter.php',
|
||||
'<?php' . PHP_EOL . '$GLOBALS[\'purge_limiter\'] = ' . $value . ';'
|
||||
);
|
||||
case 'salt':
|
||||
return self::_storeString(
|
||||
self::$_path . DIRECTORY_SEPARATOR . 'salt.php',
|
||||
return $this->_storeString(
|
||||
$this->_path . DIRECTORY_SEPARATOR . 'salt.php',
|
||||
'<?php # |' . $value . '|'
|
||||
);
|
||||
case 'traffic_limiter':
|
||||
self::$_last_cache[$key] = $value;
|
||||
return self::_storeString(
|
||||
self::$_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php',
|
||||
'<?php' . PHP_EOL . '$GLOBALS[\'traffic_limiter\'] = ' . var_export(self::$_last_cache, true) . ';'
|
||||
$this->_last_cache[$key] = $value;
|
||||
return $this->_storeString(
|
||||
$this->_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php',
|
||||
'<?php' . PHP_EOL . '$GLOBALS[\'traffic_limiter\'] = ' . var_export($this->_last_cache, true) . ';'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
|
@ -292,14 +285,14 @@ class Filesystem extends AbstractData
|
|||
{
|
||||
switch ($namespace) {
|
||||
case 'purge_limiter':
|
||||
$file = self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php';
|
||||
$file = $this->_path . DIRECTORY_SEPARATOR . 'purge_limiter.php';
|
||||
if (is_readable($file)) {
|
||||
require $file;
|
||||
return $GLOBALS['purge_limiter'];
|
||||
}
|
||||
break;
|
||||
case 'salt':
|
||||
$file = self::$_path . DIRECTORY_SEPARATOR . 'salt.php';
|
||||
$file = $this->_path . DIRECTORY_SEPARATOR . 'salt.php';
|
||||
if (is_readable($file)) {
|
||||
$items = explode('|', file_get_contents($file));
|
||||
if (is_array($items) && count($items) == 3) {
|
||||
|
@ -308,12 +301,12 @@ class Filesystem extends AbstractData
|
|||
}
|
||||
break;
|
||||
case 'traffic_limiter':
|
||||
$file = self::$_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php';
|
||||
$file = $this->_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php';
|
||||
if (is_readable($file)) {
|
||||
require $file;
|
||||
self::$_last_cache = $GLOBALS['traffic_limiter'];
|
||||
if (array_key_exists($key, self::$_last_cache)) {
|
||||
return self::$_last_cache[$key];
|
||||
$this->_last_cache = $GLOBALS['traffic_limiter'];
|
||||
if (array_key_exists($key, $this->_last_cache)) {
|
||||
return $this->_last_cache[$key];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -325,11 +318,10 @@ class Filesystem extends AbstractData
|
|||
* get the data
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @return array|false $data
|
||||
*/
|
||||
private static function _get($filename)
|
||||
private function _get($filename)
|
||||
{
|
||||
return Json::decode(
|
||||
substr(
|
||||
|
@ -349,65 +341,42 @@ class Filesystem extends AbstractData
|
|||
protected function _getExpiredPastes($batchsize)
|
||||
{
|
||||
$pastes = array();
|
||||
$firstLevel = array_filter(
|
||||
scandir(self::$_path),
|
||||
'PrivateBin\Data\Filesystem::_isFirstLevelDir'
|
||||
);
|
||||
if (count($firstLevel) > 0) {
|
||||
// try at most 10 times the $batchsize pastes before giving up
|
||||
for ($i = 0, $max = $batchsize * 10; $i < $max; ++$i) {
|
||||
$firstKey = array_rand($firstLevel);
|
||||
$secondLevel = array_filter(
|
||||
scandir(self::$_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]),
|
||||
'PrivateBin\Data\Filesystem::_isSecondLevelDir'
|
||||
);
|
||||
|
||||
// skip this folder in the next checks if it is empty
|
||||
if (count($secondLevel) == 0) {
|
||||
unset($firstLevel[$firstKey]);
|
||||
$files = $this->_getPasteIterator();
|
||||
$count = 0;
|
||||
$time = time();
|
||||
foreach ($files as $file) {
|
||||
if ($file->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$secondKey = array_rand($secondLevel);
|
||||
$path = self::$_path . DIRECTORY_SEPARATOR .
|
||||
$firstLevel[$firstKey] . DIRECTORY_SEPARATOR .
|
||||
$secondLevel[$secondKey];
|
||||
if (!is_dir($path)) {
|
||||
continue;
|
||||
}
|
||||
$thirdLevel = array_filter(
|
||||
array_map(
|
||||
function ($filename) {
|
||||
return strlen($filename) >= 20 ?
|
||||
substr($filename, 0, -4) :
|
||||
$filename;
|
||||
},
|
||||
scandir($path)
|
||||
),
|
||||
'PrivateBin\\Model\\Paste::isValidId'
|
||||
);
|
||||
if (count($thirdLevel) == 0) {
|
||||
continue;
|
||||
}
|
||||
$thirdKey = array_rand($thirdLevel);
|
||||
$pasteid = $thirdLevel[$thirdKey];
|
||||
if (in_array($pasteid, $pastes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pasteid = $file->getBasename('.php');
|
||||
if ($this->exists($pasteid)) {
|
||||
$data = $this->read($pasteid);
|
||||
if (
|
||||
array_key_exists('expire_date', $data['meta']) &&
|
||||
$data['meta']['expire_date'] < time()
|
||||
$data['meta']['expire_date'] < $time
|
||||
) {
|
||||
$pastes[] = $pasteid;
|
||||
if (count($pastes) >= $batchsize) {
|
||||
++$count;
|
||||
if ($count >= $batchsize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pastes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getAllPastes()
|
||||
{
|
||||
$pastes = array();
|
||||
$files = $this->_getPasteIterator();
|
||||
foreach ($files as $file) {
|
||||
if ($file->isFile()) {
|
||||
$pastes[] = $file->getBasename('.php');
|
||||
}
|
||||
}
|
||||
return $pastes;
|
||||
}
|
||||
|
@ -423,13 +392,12 @@ class Filesystem extends AbstractData
|
|||
* eg. input 'e3570978f9e4aa90' --> output 'data/e3/57/'
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $dataid
|
||||
* @return string
|
||||
*/
|
||||
private static function _dataid2path($dataid)
|
||||
private function _dataid2path($dataid)
|
||||
{
|
||||
return self::$_path . DIRECTORY_SEPARATOR .
|
||||
return $this->_path . DIRECTORY_SEPARATOR .
|
||||
substr($dataid, 0, 2) . DIRECTORY_SEPARATOR .
|
||||
substr($dataid, 2, 2) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
@ -440,56 +408,44 @@ class Filesystem extends AbstractData
|
|||
* eg. input 'e3570978f9e4aa90' --> output 'data/e3/57/e3570978f9e4aa90.discussion/'
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $dataid
|
||||
* @return string
|
||||
*/
|
||||
private static function _dataid2discussionpath($dataid)
|
||||
private function _dataid2discussionpath($dataid)
|
||||
{
|
||||
return self::_dataid2path($dataid) . $dataid .
|
||||
return $this->_dataid2path($dataid) . $dataid .
|
||||
'.discussion' . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the given element is a valid first level directory.
|
||||
* Get an iterator matching paste files.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $element
|
||||
* @return bool
|
||||
* @return \GlobIterator
|
||||
*/
|
||||
private static function _isFirstLevelDir($element)
|
||||
private function _getPasteIterator()
|
||||
{
|
||||
return self::_isSecondLevelDir($element) &&
|
||||
is_dir(self::$_path . DIRECTORY_SEPARATOR . $element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the given element is a valid second level directory.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $element
|
||||
* @return bool
|
||||
*/
|
||||
private static function _isSecondLevelDir($element)
|
||||
{
|
||||
return (bool) preg_match('/^[a-f0-9]{2}$/', $element);
|
||||
return new \GlobIterator($this->_path . DIRECTORY_SEPARATOR .
|
||||
'[a-f0-9][a-f0-9]' . DIRECTORY_SEPARATOR .
|
||||
'[a-f0-9][a-f0-9]' . DIRECTORY_SEPARATOR .
|
||||
'[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]' .
|
||||
'[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]*');
|
||||
// need to return both files with and without .php suffix, so they can
|
||||
// be hardened by _prependRename(), which is hooked into exists()
|
||||
}
|
||||
|
||||
/**
|
||||
* store the data
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
private static function _store($filename, array $data)
|
||||
private function _store($filename, array $data)
|
||||
{
|
||||
try {
|
||||
return self::_storeString(
|
||||
return $this->_storeString(
|
||||
$filename,
|
||||
self::PROTECTION_LINE . PHP_EOL . Json::encode($data)
|
||||
);
|
||||
|
@ -502,20 +458,19 @@ class Filesystem extends AbstractData
|
|||
* store a string
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $filename
|
||||
* @param string $data
|
||||
* @return bool
|
||||
*/
|
||||
private static function _storeString($filename, $data)
|
||||
private function _storeString($filename, $data)
|
||||
{
|
||||
// Create storage directory if it does not exist.
|
||||
if (!is_dir(self::$_path)) {
|
||||
if (!@mkdir(self::$_path, 0700)) {
|
||||
if (!is_dir($this->_path)) {
|
||||
if (!@mkdir($this->_path, 0700)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$file = self::$_path . DIRECTORY_SEPARATOR . '.htaccess';
|
||||
$file = $this->_path . DIRECTORY_SEPARATOR . '.htaccess';
|
||||
if (!is_file($file)) {
|
||||
$writtenBytes = 0;
|
||||
if ($fileCreated = @touch($file)) {
|
||||
|
@ -553,12 +508,11 @@ class Filesystem extends AbstractData
|
|||
* 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)
|
||||
private function _prependRename($srcFile, $destFile)
|
||||
{
|
||||
// don't overwrite already converted file
|
||||
if (!is_readable($destFile)) {
|
||||
|
|
|
@ -14,54 +14,43 @@ class GoogleCloudStorage extends AbstractData
|
|||
* GCS client
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var StorageClient
|
||||
*/
|
||||
private static $_client = null;
|
||||
private $_client = null;
|
||||
|
||||
/**
|
||||
* GCS bucket
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var Bucket
|
||||
*/
|
||||
private static $_bucket = null;
|
||||
private $_bucket = null;
|
||||
|
||||
/**
|
||||
* object prefix
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
private static $_prefix = 'pastes';
|
||||
private $_prefix = 'pastes';
|
||||
|
||||
/**
|
||||
* bucket acl type
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var bool
|
||||
*/
|
||||
private static $_uniformacl = false;
|
||||
private $_uniformacl = false;
|
||||
|
||||
/**
|
||||
* returns a Google Cloud Storage data backend.
|
||||
* instantiantes a new Google Cloud Storage data backend.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param array $options
|
||||
* @return GoogleCloudStorage
|
||||
* @return
|
||||
*/
|
||||
public static function getInstance(array $options)
|
||||
public function __construct(array $options)
|
||||
{
|
||||
// if needed initialize the singleton
|
||||
if (!(self::$_instance instanceof self)) {
|
||||
self::$_instance = new self;
|
||||
}
|
||||
|
||||
$bucket = null;
|
||||
if (getenv('PRIVATEBIN_GCS_BUCKET')) {
|
||||
$bucket = getenv('PRIVATEBIN_GCS_BUCKET');
|
||||
}
|
||||
|
@ -69,24 +58,22 @@ class GoogleCloudStorage extends AbstractData
|
|||
$bucket = $options['bucket'];
|
||||
}
|
||||
if (is_array($options) && array_key_exists('prefix', $options)) {
|
||||
self::$_prefix = $options['prefix'];
|
||||
$this->_prefix = $options['prefix'];
|
||||
}
|
||||
if (is_array($options) && array_key_exists('uniformacl', $options)) {
|
||||
self::$_uniformacl = $options['uniformacl'];
|
||||
$this->_uniformacl = $options['uniformacl'];
|
||||
}
|
||||
|
||||
if (empty(self::$_client)) {
|
||||
self::$_client = class_exists('StorageClientStub', false) ?
|
||||
$this->_client = class_exists('StorageClientStub', false) ?
|
||||
new \StorageClientStub(array()) :
|
||||
new StorageClient(array('suppressKeyFileNotice' => true));
|
||||
if (isset($bucket)) {
|
||||
$this->_bucket = $this->_client->bucket($bucket);
|
||||
}
|
||||
self::$_bucket = self::$_client->bucket($bucket);
|
||||
|
||||
return self::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the google storage object key for $pasteid in self::$_bucket.
|
||||
* returns the google storage object key for $pasteid in $this->_bucket.
|
||||
*
|
||||
* @access private
|
||||
* @param $pasteid string to get the key for
|
||||
|
@ -94,14 +81,14 @@ class GoogleCloudStorage extends AbstractData
|
|||
*/
|
||||
private function _getKey($pasteid)
|
||||
{
|
||||
if (self::$_prefix != '') {
|
||||
return self::$_prefix . '/' . $pasteid;
|
||||
if ($this->_prefix != '') {
|
||||
return $this->_prefix . '/' . $pasteid;
|
||||
}
|
||||
return $pasteid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the payload in the self::$_bucket under the specified key.
|
||||
* 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.
|
||||
|
@ -126,12 +113,12 @@ class GoogleCloudStorage extends AbstractData
|
|||
'metadata' => $metadata,
|
||||
),
|
||||
);
|
||||
if (!self::$_uniformacl) {
|
||||
if (!$this->_uniformacl) {
|
||||
$data['predefinedAcl'] = 'private';
|
||||
}
|
||||
self::$_bucket->upload(Json::encode($payload), $data);
|
||||
$this->_bucket->upload(Json::encode($payload), $data);
|
||||
} catch (Exception $e) {
|
||||
error_log('failed to upload ' . $key . ' to ' . self::$_bucket->name() . ', ' .
|
||||
error_log('failed to upload ' . $key . ' to ' . $this->_bucket->name() . ', ' .
|
||||
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
|
||||
return false;
|
||||
}
|
||||
|
@ -156,13 +143,13 @@ class GoogleCloudStorage extends AbstractData
|
|||
public function read($pasteid)
|
||||
{
|
||||
try {
|
||||
$o = self::$_bucket->object($this->_getKey($pasteid));
|
||||
$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 ' . self::$_bucket->name() . ', ' .
|
||||
error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket->name() . ', ' .
|
||||
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
|
||||
return false;
|
||||
}
|
||||
|
@ -176,9 +163,9 @@ class GoogleCloudStorage extends AbstractData
|
|||
$name = $this->_getKey($pasteid);
|
||||
|
||||
try {
|
||||
foreach (self::$_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) {
|
||||
foreach ($this->_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) {
|
||||
try {
|
||||
self::$_bucket->object($comment->name())->delete();
|
||||
$this->_bucket->object($comment->name())->delete();
|
||||
} catch (NotFoundException $e) {
|
||||
// ignore if already deleted.
|
||||
}
|
||||
|
@ -188,7 +175,7 @@ class GoogleCloudStorage extends AbstractData
|
|||
}
|
||||
|
||||
try {
|
||||
self::$_bucket->object($name)->delete();
|
||||
$this->_bucket->object($name)->delete();
|
||||
} catch (NotFoundException $e) {
|
||||
// ignore if already deleted
|
||||
}
|
||||
|
@ -199,7 +186,7 @@ class GoogleCloudStorage extends AbstractData
|
|||
*/
|
||||
public function exists($pasteid)
|
||||
{
|
||||
$o = self::$_bucket->object($this->_getKey($pasteid));
|
||||
$o = $this->_bucket->object($this->_getKey($pasteid));
|
||||
return $o->exists();
|
||||
}
|
||||
|
||||
|
@ -223,8 +210,8 @@ class GoogleCloudStorage extends AbstractData
|
|||
$comments = array();
|
||||
$prefix = $this->_getKey($pasteid) . '/discussion/';
|
||||
try {
|
||||
foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $key) {
|
||||
$comment = JSON::decode(self::$_bucket->object($key->name())->downloadAsString());
|
||||
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;
|
||||
|
@ -241,7 +228,7 @@ class GoogleCloudStorage extends AbstractData
|
|||
public function existsComment($pasteid, $parentid, $commentid)
|
||||
{
|
||||
$name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
|
||||
$o = self::$_bucket->object($name);
|
||||
$o = $this->_bucket->object($name);
|
||||
return $o->exists();
|
||||
}
|
||||
|
||||
|
@ -252,7 +239,7 @@ class GoogleCloudStorage extends AbstractData
|
|||
{
|
||||
$path = 'config/' . $namespace;
|
||||
try {
|
||||
foreach (self::$_bucket->objects(array('prefix' => $path)) as $object) {
|
||||
foreach ($this->_bucket->objects(array('prefix' => $path)) as $object) {
|
||||
$name = $object->name();
|
||||
if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') {
|
||||
continue;
|
||||
|
@ -300,12 +287,12 @@ class GoogleCloudStorage extends AbstractData
|
|||
'metadata' => $metadata,
|
||||
),
|
||||
);
|
||||
if (!self::$_uniformacl) {
|
||||
if (!$this->_uniformacl) {
|
||||
$data['predefinedAcl'] = 'private';
|
||||
}
|
||||
self::$_bucket->upload($value, $data);
|
||||
$this->_bucket->upload($value, $data);
|
||||
} catch (Exception $e) {
|
||||
error_log('failed to set key ' . $key . ' to ' . self::$_bucket->name() . ', ' .
|
||||
error_log('failed to set key ' . $key . ' to ' . $this->_bucket->name() . ', ' .
|
||||
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
|
||||
return false;
|
||||
}
|
||||
|
@ -323,7 +310,7 @@ class GoogleCloudStorage extends AbstractData
|
|||
$key = 'config/' . $namespace . '/' . $key;
|
||||
}
|
||||
try {
|
||||
$o = self::$_bucket->object($key);
|
||||
$o = $this->_bucket->object($key);
|
||||
return $o->downloadAsString();
|
||||
} catch (NotFoundException $e) {
|
||||
return '';
|
||||
|
@ -338,12 +325,12 @@ class GoogleCloudStorage extends AbstractData
|
|||
$expired = array();
|
||||
|
||||
$now = time();
|
||||
$prefix = self::$_prefix;
|
||||
$prefix = $this->_prefix;
|
||||
if ($prefix != '') {
|
||||
$prefix .= '/';
|
||||
}
|
||||
try {
|
||||
foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $object) {
|
||||
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']);
|
||||
|
@ -361,4 +348,28 @@ class GoogleCloudStorage extends AbstractData
|
|||
}
|
||||
return $expired;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getAllPastes()
|
||||
{
|
||||
$pastes = array();
|
||||
$prefix = $this->_prefix;
|
||||
if ($prefix != '') {
|
||||
$prefix .= '/';
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) {
|
||||
$candidate = substr($object->name(), strlen($prefix));
|
||||
if (strpos($candidate, '/') === false) {
|
||||
$pastes[] = $candidate;
|
||||
}
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
// no objects in the bucket yet
|
||||
}
|
||||
return $pastes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,86 +45,71 @@ class S3Storage extends AbstractData
|
|||
* S3 client
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var S3Client
|
||||
*/
|
||||
private static $_client = null;
|
||||
private $_client = null;
|
||||
|
||||
/**
|
||||
* S3 client options
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var array
|
||||
*/
|
||||
private static $_options = array();
|
||||
private $_options = array();
|
||||
|
||||
/**
|
||||
* S3 bucket
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
private static $_bucket = null;
|
||||
private $_bucket = null;
|
||||
|
||||
/**
|
||||
* S3 prefix for all PrivateBin data in this bucket
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
private static $_prefix = '';
|
||||
private $_prefix = '';
|
||||
|
||||
/**
|
||||
* returns an S3 data backend.
|
||||
* instantiates a new S3 data backend.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param array $options
|
||||
* @return S3Storage
|
||||
* @return
|
||||
*/
|
||||
public static function getInstance(array $options)
|
||||
public function __construct(array $options)
|
||||
{
|
||||
// if needed initialize the singleton
|
||||
if (!(self::$_instance instanceof self)) {
|
||||
self::$_instance = new self;
|
||||
}
|
||||
|
||||
self::$_options = array();
|
||||
self::$_options['credentials'] = array();
|
||||
$this->_options['credentials'] = array();
|
||||
|
||||
if (is_array($options) && array_key_exists('region', $options)) {
|
||||
self::$_options['region'] = $options['region'];
|
||||
$this->_options['region'] = $options['region'];
|
||||
}
|
||||
if (is_array($options) && array_key_exists('version', $options)) {
|
||||
self::$_options['version'] = $options['version'];
|
||||
$this->_options['version'] = $options['version'];
|
||||
}
|
||||
if (is_array($options) && array_key_exists('endpoint', $options)) {
|
||||
self::$_options['endpoint'] = $options['endpoint'];
|
||||
$this->_options['endpoint'] = $options['endpoint'];
|
||||
}
|
||||
if (is_array($options) && array_key_exists('accesskey', $options)) {
|
||||
self::$_options['credentials']['key'] = $options['accesskey'];
|
||||
$this->_options['credentials']['key'] = $options['accesskey'];
|
||||
}
|
||||
if (is_array($options) && array_key_exists('secretkey', $options)) {
|
||||
self::$_options['credentials']['secret'] = $options['secretkey'];
|
||||
$this->_options['credentials']['secret'] = $options['secretkey'];
|
||||
}
|
||||
if (is_array($options) && array_key_exists('use_path_style_endpoint', $options)) {
|
||||
self::$_options['use_path_style_endpoint'] = filter_var($options['use_path_style_endpoint'], FILTER_VALIDATE_BOOLEAN);
|
||||
$this->_options['use_path_style_endpoint'] = filter_var($options['use_path_style_endpoint'], FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
if (is_array($options) && array_key_exists('bucket', $options)) {
|
||||
self::$_bucket = $options['bucket'];
|
||||
$this->_bucket = $options['bucket'];
|
||||
}
|
||||
if (is_array($options) && array_key_exists('prefix', $options)) {
|
||||
self::$_prefix = $options['prefix'];
|
||||
$this->_prefix = $options['prefix'];
|
||||
}
|
||||
|
||||
if (empty(self::$_client)) {
|
||||
self::$_client = new S3Client(self::$_options);
|
||||
}
|
||||
|
||||
return self::$_instance;
|
||||
$this->_client = new S3Client($this->_options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,12 +123,12 @@ class S3Storage extends AbstractData
|
|||
{
|
||||
$allObjects = array();
|
||||
$options = array(
|
||||
'Bucket' => self::$_bucket,
|
||||
'Bucket' => $this->_bucket,
|
||||
'Prefix' => $prefix,
|
||||
);
|
||||
|
||||
do {
|
||||
$objectsListResponse = self::$_client->listObjects($options);
|
||||
$objectsListResponse = $this->_client->listObjects($options);
|
||||
$objects = $objectsListResponse['Contents'] ?? array();
|
||||
foreach ($objects as $object) {
|
||||
$allObjects[] = $object;
|
||||
|
@ -155,7 +140,7 @@ class S3Storage extends AbstractData
|
|||
}
|
||||
|
||||
/**
|
||||
* returns the S3 storage object key for $pasteid in self::$_bucket.
|
||||
* returns the S3 storage object key for $pasteid in $this->_bucket.
|
||||
*
|
||||
* @access private
|
||||
* @param $pasteid string to get the key for
|
||||
|
@ -163,14 +148,14 @@ class S3Storage extends AbstractData
|
|||
*/
|
||||
private function _getKey($pasteid)
|
||||
{
|
||||
if (self::$_prefix != '') {
|
||||
return self::$_prefix . '/' . $pasteid;
|
||||
if ($this->_prefix != '') {
|
||||
return $this->_prefix . '/' . $pasteid;
|
||||
}
|
||||
return $pasteid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the payload in the self::$_bucket under the specified key.
|
||||
* 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 S3 object's metadata except for the fields attachment, attachmentname
|
||||
* and salt.
|
||||
|
@ -187,15 +172,15 @@ class S3Storage extends AbstractData
|
|||
$metadata[$k] = strval($v);
|
||||
}
|
||||
try {
|
||||
self::$_client->putObject(array(
|
||||
'Bucket' => self::$_bucket,
|
||||
$this->_client->putObject(array(
|
||||
'Bucket' => $this->_bucket,
|
||||
'Key' => $key,
|
||||
'Body' => Json::encode($payload),
|
||||
'ContentType' => 'application/json',
|
||||
'Metadata' => $metadata,
|
||||
));
|
||||
} catch (S3Exception $e) {
|
||||
error_log('failed to upload ' . $key . ' to ' . self::$_bucket . ', ' .
|
||||
error_log('failed to upload ' . $key . ' to ' . $this->_bucket . ', ' .
|
||||
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
|
||||
return false;
|
||||
}
|
||||
|
@ -220,14 +205,14 @@ class S3Storage extends AbstractData
|
|||
public function read($pasteid)
|
||||
{
|
||||
try {
|
||||
$object = self::$_client->getObject(array(
|
||||
'Bucket' => self::$_bucket,
|
||||
$object = $this->_client->getObject(array(
|
||||
'Bucket' => $this->_bucket,
|
||||
'Key' => $this->_getKey($pasteid),
|
||||
));
|
||||
$data = $object['Body']->getContents();
|
||||
return Json::decode($data);
|
||||
} catch (S3Exception $e) {
|
||||
error_log('failed to read ' . $pasteid . ' from ' . self::$_bucket . ', ' .
|
||||
error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket . ', ' .
|
||||
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
|
||||
return false;
|
||||
}
|
||||
|
@ -244,8 +229,8 @@ class S3Storage extends AbstractData
|
|||
$comments = $this->_listAllObjects($name . '/discussion/');
|
||||
foreach ($comments as $comment) {
|
||||
try {
|
||||
self::$_client->deleteObject(array(
|
||||
'Bucket' => self::$_bucket,
|
||||
$this->_client->deleteObject(array(
|
||||
'Bucket' => $this->_bucket,
|
||||
'Key' => $comment['Key'],
|
||||
));
|
||||
} catch (S3Exception $e) {
|
||||
|
@ -257,8 +242,8 @@ class S3Storage extends AbstractData
|
|||
}
|
||||
|
||||
try {
|
||||
self::$_client->deleteObject(array(
|
||||
'Bucket' => self::$_bucket,
|
||||
$this->_client->deleteObject(array(
|
||||
'Bucket' => $this->_bucket,
|
||||
'Key' => $name,
|
||||
));
|
||||
} catch (S3Exception $e) {
|
||||
|
@ -271,7 +256,7 @@ class S3Storage extends AbstractData
|
|||
*/
|
||||
public function exists($pasteid)
|
||||
{
|
||||
return self::$_client->doesObjectExistV2(self::$_bucket, $this->_getKey($pasteid));
|
||||
return $this->_client->doesObjectExistV2($this->_bucket, $this->_getKey($pasteid));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -296,8 +281,8 @@ class S3Storage extends AbstractData
|
|||
try {
|
||||
$entries = $this->_listAllObjects($prefix);
|
||||
foreach ($entries as $entry) {
|
||||
$object = self::$_client->getObject(array(
|
||||
'Bucket' => self::$_bucket,
|
||||
$object = $this->_client->getObject(array(
|
||||
'Bucket' => $this->_bucket,
|
||||
'Key' => $entry['Key'],
|
||||
));
|
||||
$body = JSON::decode($object['Body']->getContents());
|
||||
|
@ -319,7 +304,7 @@ class S3Storage extends AbstractData
|
|||
public function existsComment($pasteid, $parentid, $commentid)
|
||||
{
|
||||
$name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
|
||||
return self::$_client->doesObjectExistV2(self::$_bucket, $name);
|
||||
return $this->_client->doesObjectExistV2($this->_bucket, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -327,7 +312,7 @@ class S3Storage extends AbstractData
|
|||
*/
|
||||
public function purgeValues($namespace, $time)
|
||||
{
|
||||
$path = self::$_prefix;
|
||||
$path = $this->_prefix;
|
||||
if ($path != '') {
|
||||
$path .= '/';
|
||||
}
|
||||
|
@ -339,16 +324,16 @@ class S3Storage extends AbstractData
|
|||
if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') {
|
||||
continue;
|
||||
}
|
||||
$head = self::$_client->headObject(array(
|
||||
'Bucket' => self::$_bucket,
|
||||
$head = $this->_client->headObject(array(
|
||||
'Bucket' => $this->_bucket,
|
||||
'Key' => $name,
|
||||
));
|
||||
if (array_key_exists('Metadata', $head) && array_key_exists('value', $head['Metadata'])) {
|
||||
$value = $head['Metadata']['value'];
|
||||
if ($head->get('Metadata') != null && array_key_exists('value', $head->get('Metadata'))) {
|
||||
$value = $head->get('Metadata')['value'];
|
||||
if (is_numeric($value) && intval($value) < $time) {
|
||||
try {
|
||||
self::$_client->deleteObject(array(
|
||||
'Bucket' => self::$_bucket,
|
||||
$this->_client->deleteObject(array(
|
||||
'Bucket' => $this->_bucket,
|
||||
'Key' => $name,
|
||||
));
|
||||
} catch (S3Exception $e) {
|
||||
|
@ -369,7 +354,7 @@ class S3Storage extends AbstractData
|
|||
*/
|
||||
public function setValue($value, $namespace, $key = '')
|
||||
{
|
||||
$prefix = self::$_prefix;
|
||||
$prefix = $this->_prefix;
|
||||
if ($prefix != '') {
|
||||
$prefix .= '/';
|
||||
}
|
||||
|
@ -385,15 +370,15 @@ class S3Storage extends AbstractData
|
|||
$metadata['value'] = strval($value);
|
||||
}
|
||||
try {
|
||||
self::$_client->putObject(array(
|
||||
'Bucket' => self::$_bucket,
|
||||
$this->_client->putObject(array(
|
||||
'Bucket' => $this->_bucket,
|
||||
'Key' => $key,
|
||||
'Body' => $value,
|
||||
'ContentType' => 'application/json',
|
||||
'Metadata' => $metadata,
|
||||
));
|
||||
} catch (S3Exception $e) {
|
||||
error_log('failed to set key ' . $key . ' to ' . self::$_bucket . ', ' .
|
||||
error_log('failed to set key ' . $key . ' to ' . $this->_bucket . ', ' .
|
||||
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
|
||||
return false;
|
||||
}
|
||||
|
@ -405,7 +390,7 @@ class S3Storage extends AbstractData
|
|||
*/
|
||||
public function getValue($namespace, $key = '')
|
||||
{
|
||||
$prefix = self::$_prefix;
|
||||
$prefix = $this->_prefix;
|
||||
if ($prefix != '') {
|
||||
$prefix .= '/';
|
||||
}
|
||||
|
@ -417,8 +402,8 @@ class S3Storage extends AbstractData
|
|||
}
|
||||
|
||||
try {
|
||||
$object = self::$_client->getObject(array(
|
||||
'Bucket' => self::$_bucket,
|
||||
$object = $this->_client->getObject(array(
|
||||
'Bucket' => $this->_bucket,
|
||||
'Key' => $key,
|
||||
));
|
||||
return $object['Body']->getContents();
|
||||
|
@ -434,19 +419,19 @@ class S3Storage extends AbstractData
|
|||
{
|
||||
$expired = array();
|
||||
$now = time();
|
||||
$prefix = self::$_prefix;
|
||||
$prefix = $this->_prefix;
|
||||
if ($prefix != '') {
|
||||
$prefix .= '/';
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($this->_listAllObjects($prefix) as $object) {
|
||||
$head = self::$_client->headObject(array(
|
||||
'Bucket' => self::$_bucket,
|
||||
$head = $this->_client->headObject(array(
|
||||
'Bucket' => $this->_bucket,
|
||||
'Key' => $object['Key'],
|
||||
));
|
||||
if (array_key_exists('Metadata', $head) && array_key_exists('expire_date', $head['Metadata'])) {
|
||||
$expire_at = intval($head['Metadata']['expire_date']);
|
||||
if ($head->get('Metadata') != null && array_key_exists('expire_date', $head->get('Metadata'))) {
|
||||
$expire_at = intval($head->get('Metadata')['expire_date']);
|
||||
if ($expire_at != 0 && $expire_at < $now) {
|
||||
array_push($expired, $object['Key']);
|
||||
}
|
||||
|
@ -461,4 +446,28 @@ class S3Storage extends AbstractData
|
|||
}
|
||||
return $expired;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getAllPastes()
|
||||
{
|
||||
$pastes = array();
|
||||
$prefix = $this->_prefix;
|
||||
if ($prefix != '') {
|
||||
$prefix .= '/';
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($this->_listAllObjects($prefix) as $object) {
|
||||
$candidate = substr($object['Key'], strlen($prefix));
|
||||
if (strpos($candidate, '/') === false) {
|
||||
$pastes[] = $candidate;
|
||||
}
|
||||
}
|
||||
} catch (S3Exception $e) {
|
||||
// no objects in the bucket yet
|
||||
}
|
||||
return $pastes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -328,6 +328,7 @@ class I18n
|
|||
return $n === 1 ? 0 : ($n === 2 ? 1 : (($n < 0 || $n > 10) && ($n % 10 === 0) ? 2 : 3));
|
||||
case 'id':
|
||||
case 'jbo':
|
||||
case 'th':
|
||||
return 0;
|
||||
case 'lt':
|
||||
return $n % 10 === 1 && $n % 100 !== 11 ? 0 : (($n % 10 >= 2 && $n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
|
||||
|
|
|
@ -81,10 +81,8 @@ class Model
|
|||
public function getStore()
|
||||
{
|
||||
if ($this->_store === null) {
|
||||
$this->_store = forward_static_call(
|
||||
'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model') . '::getInstance',
|
||||
$this->_conf->getSection('model_options')
|
||||
);
|
||||
$class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model');
|
||||
$this->_store = new $class($this->_conf->getSection('model_options'));
|
||||
}
|
||||
return $this->_store;
|
||||
}
|
||||
|
|
|
@ -165,7 +165,10 @@ class Comment extends AbstractModel
|
|||
if ($icon != 'none') {
|
||||
$pngdata = '';
|
||||
$hmac = TrafficLimiter::getHash();
|
||||
if ($icon == 'jdenticon') {
|
||||
if ($icon == 'identicon') {
|
||||
$identicon = new Identicon();
|
||||
$pngdata = $identicon->getImageDataUri($hmac, 16);
|
||||
} elseif ($icon == 'jdenticon') {
|
||||
$jdenticon = new Jdenticon(array(
|
||||
'hash' => $hmac,
|
||||
'size' => 16,
|
||||
|
@ -175,9 +178,6 @@ class Comment extends AbstractModel
|
|||
),
|
||||
));
|
||||
$pngdata = $jdenticon->getImageDataUri('png');
|
||||
} elseif ($icon == 'identicon') {
|
||||
$identicon = new Identicon();
|
||||
$pngdata = $identicon->getImageDataUri($hmac, 16);
|
||||
} elseif ($icon == 'vizhash') {
|
||||
$vh = new Vizhash16x16();
|
||||
$pngdata = 'data:image/png;base64,' . base64_encode(
|
||||
|
|
|
@ -42,7 +42,7 @@ if ($SYNTAXHIGHLIGHTING) :
|
|||
endif;
|
||||
?>
|
||||
<noscript><link type="text/css" rel="stylesheet" href="css/noscript.css" /></noscript>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/jquery-3.6.0.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/jquery-3.6.1.js" integrity="sha512-aVKKRRi/Q/YV+4mjoKBsE4x3H+BkegoM/em46NNlCqNTmUYADjBbeNefNxYV7giUp0VxICtqdrbqU7iVaeZNXA==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
if ($QRCODE) :
|
||||
?>
|
||||
|
@ -67,13 +67,13 @@ if ($SYNTAXHIGHLIGHTING) :
|
|||
endif;
|
||||
if ($MARKDOWN) :
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.3.js" integrity="sha512-vcfjvW3UKHD/4vlQx804cqWK88jFmjsWRsZ8/u5YEcyHB1IituxrXDU7TvdqsFVsMnxpE/UIEo25/SYW+puWHw==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.1.0.js" integrity="sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.4.6.js" integrity="sha512-+jcx+EqNbaFT4OHS86zGwU1SNAAZ7hG2pJlwMpXoe9AvTp37BrXMQ29g2GhdyQHTvYWaNlTQIkWXYM0Lvt8GiQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-sQhu+q8ayRj0LO80orHnhEudlTf5Qx+Yhb/+U84ixMvSdwijuLEHGiEuWctqUPOFSe56L6aeq6z7K0ONpbgaew==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-0he/s7UnjLf2pxLH+EIGImaUBhVzpngrL5PId/QB2FcstucrPUPOhQpqJcGvtmVPyuXckiktEwwrz4Sn4Mi3Tw==" crossorigin="anonymous"></script>
|
||||
<!-- icon -->
|
||||
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
|
||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />
|
||||
|
|
|
@ -21,7 +21,7 @@ if ($SYNTAXHIGHLIGHTING):
|
|||
endif;
|
||||
endif;
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/jquery-3.6.0.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/jquery-3.6.1.js" integrity="sha512-aVKKRRi/Q/YV+4mjoKBsE4x3H+BkegoM/em46NNlCqNTmUYADjBbeNefNxYV7giUp0VxICtqdrbqU7iVaeZNXA==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
if ($QRCODE):
|
||||
?>
|
||||
|
@ -45,13 +45,13 @@ if ($SYNTAXHIGHLIGHTING):
|
|||
endif;
|
||||
if ($MARKDOWN):
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.0.3.js" integrity="sha512-vcfjvW3UKHD/4vlQx804cqWK88jFmjsWRsZ8/u5YEcyHB1IituxrXDU7TvdqsFVsMnxpE/UIEo25/SYW+puWHw==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/showdown-2.1.0.js" integrity="sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==" crossorigin="anonymous"></script>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/purify-2.4.6.js" integrity="sha512-+jcx+EqNbaFT4OHS86zGwU1SNAAZ7hG2pJlwMpXoe9AvTp37BrXMQ29g2GhdyQHTvYWaNlTQIkWXYM0Lvt8GiQ==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-sQhu+q8ayRj0LO80orHnhEudlTf5Qx+Yhb/+U84ixMvSdwijuLEHGiEuWctqUPOFSe56L6aeq6z7K0ONpbgaew==" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-0he/s7UnjLf2pxLH+EIGImaUBhVzpngrL5PId/QB2FcstucrPUPOhQpqJcGvtmVPyuXckiktEwwrz4Sn4Mi3Tw==" crossorigin="anonymous"></script>
|
||||
<!-- icon -->
|
||||
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
|
||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
|
||||
|
|
|
@ -34,7 +34,7 @@ class StorageClientStub extends StorageClient
|
|||
{
|
||||
private $_config = null;
|
||||
private $_connection = null;
|
||||
private $_buckets = array();
|
||||
private static $_buckets = array();
|
||||
|
||||
public function __construct(array $config = array())
|
||||
{
|
||||
|
@ -44,11 +44,11 @@ class StorageClientStub extends StorageClient
|
|||
|
||||
public function bucket($name, $userProject = false)
|
||||
{
|
||||
if (!key_exists($name, $this->_buckets)) {
|
||||
if (!key_exists($name, self::$_buckets)) {
|
||||
$b = new BucketStub($this->_connection, $name, array(), $this);
|
||||
$this->_buckets[$name] = $b;
|
||||
self::$_buckets[$name] = $b;
|
||||
}
|
||||
return $this->_buckets[$name];
|
||||
return self::$_buckets[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,8 +56,8 @@ class StorageClientStub extends StorageClient
|
|||
*/
|
||||
public function deleteBucket($name)
|
||||
{
|
||||
if (key_exists($name, $this->_buckets)) {
|
||||
unset($this->_buckets[$name]);
|
||||
if (key_exists($name, self::$_buckets)) {
|
||||
unset(self::$_buckets[$name]);
|
||||
} else {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
@ -110,11 +110,11 @@ class StorageClientStub extends StorageClient
|
|||
|
||||
public function createBucket($name, array $options = array())
|
||||
{
|
||||
if (key_exists($name, $this->_buckets)) {
|
||||
if (key_exists($name, self::$_buckets)) {
|
||||
throw new BadRequestException('already exists');
|
||||
}
|
||||
$b = new BucketStub($this->_connection, $name, array(), $this);
|
||||
$this->_buckets[$name] = $b;
|
||||
self::$_buckets[$name] = $b;
|
||||
return $b;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class ControllerTest extends TestCase
|
|||
{
|
||||
/* Setup Routine */
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
|
||||
$this->_data = Filesystem::getInstance(array('dir' => $this->_path));
|
||||
$this->_data = new Filesystem(array('dir' => $this->_path));
|
||||
ServerSalt::setStore($this->_data);
|
||||
TrafficLimiter::setStore($this->_data);
|
||||
$this->reset();
|
||||
|
|
|
@ -25,7 +25,7 @@ class ControllerWithDbTest extends ControllerTest
|
|||
mkdir($this->_path);
|
||||
}
|
||||
$this->_options['dsn'] = 'sqlite:' . $this->_path . DIRECTORY_SEPARATOR . 'tst.sq3';
|
||||
$this->_data = Database::getInstance($this->_options);
|
||||
$this->_data = new Database($this->_options);
|
||||
ServerSalt::setStore($this->_data);
|
||||
TrafficLimiter::setStore($this->_data);
|
||||
$this->reset();
|
||||
|
|
|
@ -39,7 +39,7 @@ class ControllerWithGcsTest extends ControllerTest
|
|||
'bucket' => self::$_bucket->name(),
|
||||
'prefix' => 'pastes',
|
||||
);
|
||||
$this->_data = GoogleCloudStorage::getInstance($this->_options);
|
||||
$this->_data = new GoogleCloudStorage($this->_options);
|
||||
ServerSalt::setStore($this->_data);
|
||||
TrafficLimiter::setStore($this->_data);
|
||||
$this->reset();
|
||||
|
|
|
@ -23,7 +23,7 @@ class DatabaseTest extends TestCase
|
|||
{
|
||||
/* Setup Routine */
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
|
||||
$this->_model = Database::getInstance($this->_options);
|
||||
$this->_model = new Database($this->_options);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
|
@ -36,7 +36,7 @@ class DatabaseTest extends TestCase
|
|||
|
||||
public function testSaltMigration()
|
||||
{
|
||||
ServerSalt::setStore(Filesystem::getInstance(array('dir' => 'data')));
|
||||
ServerSalt::setStore(new Filesystem(array('dir' => 'data')));
|
||||
$salt = ServerSalt::get();
|
||||
$file = 'data' . DIRECTORY_SEPARATOR . 'salt.php';
|
||||
$this->assertFileExists($file, 'ServerSalt got initialized and stored on disk');
|
||||
|
@ -140,7 +140,7 @@ class DatabaseTest extends TestCase
|
|||
public function testGetIbmInstance()
|
||||
{
|
||||
$this->expectException(PDOException::class);
|
||||
Database::getInstance(array(
|
||||
new Database(array(
|
||||
'dsn' => 'ibm:', 'usr' => null, 'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
));
|
||||
|
@ -149,7 +149,7 @@ class DatabaseTest extends TestCase
|
|||
public function testGetInformixInstance()
|
||||
{
|
||||
$this->expectException(PDOException::class);
|
||||
Database::getInstance(array(
|
||||
new Database(array(
|
||||
'dsn' => 'informix:', 'usr' => null, 'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
));
|
||||
|
@ -158,7 +158,7 @@ class DatabaseTest extends TestCase
|
|||
public function testGetMssqlInstance()
|
||||
{
|
||||
$this->expectException(PDOException::class);
|
||||
Database::getInstance(array(
|
||||
new Database(array(
|
||||
'dsn' => 'mssql:', 'usr' => null, 'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
));
|
||||
|
@ -167,7 +167,7 @@ class DatabaseTest extends TestCase
|
|||
public function testGetMysqlInstance()
|
||||
{
|
||||
$this->expectException(PDOException::class);
|
||||
Database::getInstance(array(
|
||||
new Database(array(
|
||||
'dsn' => 'mysql:', 'usr' => null, 'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
));
|
||||
|
@ -176,7 +176,7 @@ class DatabaseTest extends TestCase
|
|||
public function testGetOciInstance()
|
||||
{
|
||||
$this->expectException(PDOException::class);
|
||||
Database::getInstance(array(
|
||||
new Database(array(
|
||||
'dsn' => 'oci:', 'usr' => null, 'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
));
|
||||
|
@ -185,7 +185,7 @@ class DatabaseTest extends TestCase
|
|||
public function testGetPgsqlInstance()
|
||||
{
|
||||
$this->expectException(PDOException::class);
|
||||
Database::getInstance(array(
|
||||
new Database(array(
|
||||
'dsn' => 'pgsql:', 'usr' => null, 'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
));
|
||||
|
@ -195,7 +195,7 @@ class DatabaseTest extends TestCase
|
|||
{
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionCode(5);
|
||||
Database::getInstance(array(
|
||||
new Database(array(
|
||||
'dsn' => 'foo:', 'usr' => null, 'pwd' => null, 'opt' => null,
|
||||
));
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ class DatabaseTest extends TestCase
|
|||
unset($options['dsn']);
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionCode(6);
|
||||
Database::getInstance($options);
|
||||
new Database($options);
|
||||
}
|
||||
|
||||
public function testMissingUsr()
|
||||
|
@ -215,7 +215,7 @@ class DatabaseTest extends TestCase
|
|||
unset($options['usr']);
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionCode(6);
|
||||
Database::getInstance($options);
|
||||
new Database($options);
|
||||
}
|
||||
|
||||
public function testMissingPwd()
|
||||
|
@ -224,7 +224,7 @@ class DatabaseTest extends TestCase
|
|||
unset($options['pwd']);
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionCode(6);
|
||||
Database::getInstance($options);
|
||||
new Database($options);
|
||||
}
|
||||
|
||||
public function testMissingOpt()
|
||||
|
@ -233,7 +233,7 @@ class DatabaseTest extends TestCase
|
|||
unset($options['opt']);
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionCode(6);
|
||||
Database::getInstance($options);
|
||||
new Database($options);
|
||||
}
|
||||
|
||||
public function testOldAttachments()
|
||||
|
@ -245,7 +245,7 @@ class DatabaseTest extends TestCase
|
|||
}
|
||||
$this->_options['dsn'] = 'sqlite:' . $path;
|
||||
$this->_options['tbl'] = 'bar_';
|
||||
$model = Database::getInstance($this->_options);
|
||||
$model = new Database($this->_options);
|
||||
|
||||
$original = $paste = Helper::getPasteWithAttachment(1, array('expire_date' => 1344803344));
|
||||
$meta = $paste['meta'];
|
||||
|
@ -290,7 +290,7 @@ class DatabaseTest extends TestCase
|
|||
}
|
||||
$this->_options['dsn'] = 'sqlite:' . $path;
|
||||
$this->_options['tbl'] = 'baz_';
|
||||
$model = Database::getInstance($this->_options);
|
||||
$model = new Database($this->_options);
|
||||
$paste = Helper::getPaste(1, array('expire_date' => 1344803344));
|
||||
unset($paste['meta']['formatter'], $paste['meta']['opendiscussion'], $paste['meta']['salt']);
|
||||
$model->delete(Helper::getPasteId());
|
||||
|
@ -357,7 +357,7 @@ class DatabaseTest extends TestCase
|
|||
'vizhash BLOB, ' .
|
||||
'postdate INT );'
|
||||
);
|
||||
$this->assertInstanceOf('PrivateBin\\Data\\Database', Database::getInstance($this->_options));
|
||||
$this->assertInstanceOf('PrivateBin\\Data\\Database', new Database($this->_options));
|
||||
|
||||
// check if version number was upgraded in created configuration table
|
||||
$statement = $db->prepare('SELECT value FROM foo_config WHERE id LIKE ?');
|
||||
|
|
|
@ -16,7 +16,7 @@ class FilesystemTest extends TestCase
|
|||
/* Setup Routine */
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
|
||||
$this->_invalidPath = $this->_path . DIRECTORY_SEPARATOR . 'bar';
|
||||
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
|
||||
$this->_model = new Filesystem(array('dir' => $this->_path));
|
||||
if (!is_dir($this->_path)) {
|
||||
mkdir($this->_path);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class GoogleCloudStorageTest extends TestCase
|
|||
public function setUp(): void
|
||||
{
|
||||
ini_set('error_log', stream_get_meta_data(tmpfile())['uri']);
|
||||
$this->_model = GoogleCloudStorage::getInstance(array(
|
||||
$this->_model = new GoogleCloudStorage(array(
|
||||
'bucket' => self::$_bucket->name(),
|
||||
'prefix' => 'pastes',
|
||||
));
|
||||
|
|
|
@ -19,7 +19,7 @@ class JsonApiTest extends TestCase
|
|||
if (!is_dir($this->_path)) {
|
||||
mkdir($this->_path);
|
||||
}
|
||||
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
|
||||
$this->_model = new Filesystem(array('dir' => $this->_path));
|
||||
ServerSalt::setStore($this->_model);
|
||||
|
||||
$_POST = array();
|
||||
|
|
82
tst/MigrateTest.php
Normal file
82
tst/MigrateTest.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PrivateBin\Data\Database;
|
||||
use PrivateBin\Data\Filesystem;
|
||||
|
||||
class MigrateTest extends TestCase
|
||||
{
|
||||
protected $_model_1;
|
||||
|
||||
protected $_model_2;
|
||||
|
||||
protected $_path;
|
||||
|
||||
protected $_path_instance_1;
|
||||
|
||||
protected $_path_instance_2;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
/* Setup Routine */
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
|
||||
$this->_path_instance_1 = $this->_path . DIRECTORY_SEPARATOR . 'instance_1';
|
||||
$this->_path_instance_2 = $this->_path . DIRECTORY_SEPARATOR . 'instance_2';
|
||||
if (!is_dir($this->_path)) {
|
||||
mkdir($this->_path);
|
||||
}
|
||||
mkdir($this->_path_instance_1);
|
||||
mkdir($this->_path_instance_1 . DIRECTORY_SEPARATOR . 'cfg');
|
||||
mkdir($this->_path_instance_2);
|
||||
mkdir($this->_path_instance_2 . DIRECTORY_SEPARATOR . 'cfg');
|
||||
$options = parse_ini_file(CONF_SAMPLE, true);
|
||||
$options['purge']['limit'] = 0;
|
||||
$options['model_options']['dir'] = $this->_path_instance_1 . DIRECTORY_SEPARATOR . 'data';
|
||||
$this->_model_1 = new Filesystem($options['model_options']);
|
||||
Helper::createIniFile($this->_path_instance_1 . DIRECTORY_SEPARATOR . 'cfg' . DIRECTORY_SEPARATOR . 'conf.php', $options);
|
||||
|
||||
$options['model'] = array(
|
||||
'class' => 'Database',
|
||||
);
|
||||
$options['model_options'] = array(
|
||||
'dsn' => 'sqlite:' . $this->_path_instance_2 . DIRECTORY_SEPARATOR . 'test.sq3',
|
||||
'usr' => null,
|
||||
'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
);
|
||||
$this->_model_2 = new Database($options['model_options']);
|
||||
Helper::createIniFile($this->_path_instance_2 . DIRECTORY_SEPARATOR . 'cfg' . DIRECTORY_SEPARATOR . 'conf.php', $options);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
/* Tear Down Routine */
|
||||
Helper::rmDir($this->_path);
|
||||
}
|
||||
|
||||
public function testMigrate()
|
||||
{
|
||||
$this->_model_1->delete(Helper::getPasteId());
|
||||
$this->_model_2->delete(Helper::getPasteId());
|
||||
|
||||
// storing paste & comment
|
||||
$this->_model_1->create(Helper::getPasteId(), Helper::getPaste());
|
||||
$this->_model_1->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), Helper::getComment());
|
||||
|
||||
// migrate files to database
|
||||
$output = null;
|
||||
$exit_code = 255;
|
||||
exec('php ' . PATH . 'bin' . DIRECTORY_SEPARATOR . 'migrate --delete-after ' . $this->_path_instance_1 . DIRECTORY_SEPARATOR . 'cfg ' . $this->_path_instance_2 . DIRECTORY_SEPARATOR . 'cfg', $output, $exit_code);
|
||||
$this->assertEquals(0, $exit_code, 'migrate script exits 0');
|
||||
$this->assertFalse($this->_model_1->exists(Helper::getPasteId()), 'paste removed after migrating it');
|
||||
$this->assertFalse($this->_model_1->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment removed after migrating it');
|
||||
$this->assertTrue($this->_model_2->exists(Helper::getPasteId()), 'paste migrated');
|
||||
$this->assertTrue($this->_model_2->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment migrated');
|
||||
|
||||
// migrate back to files
|
||||
$exit_code = 255;
|
||||
exec('php ' . PATH . 'bin' . DIRECTORY_SEPARATOR . 'migrate ' . $this->_path_instance_2 . DIRECTORY_SEPARATOR . 'cfg ' . $this->_path_instance_1 . DIRECTORY_SEPARATOR . 'cfg', $output, $exit_code);
|
||||
$this->assertEquals(0, $exit_code, 'migrate script exits 0');
|
||||
$this->assertTrue($this->_model_1->exists(Helper::getPasteId()), 'paste migrated back');
|
||||
$this->assertTrue($this->_model_1->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment migrated back');
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Jdenticon\Identicon;
|
||||
use Identicon\Identicon;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PrivateBin\Configuration;
|
||||
use PrivateBin\Data\Database;
|
||||
|
@ -39,7 +39,7 @@ class ModelTest extends TestCase
|
|||
);
|
||||
Helper::confBackup();
|
||||
Helper::createIniFile(CONF, $options);
|
||||
ServerSalt::setStore(Database::getInstance($options['model_options']));
|
||||
ServerSalt::setStore(new Database($options['model_options']));
|
||||
$this->_conf = new Configuration;
|
||||
$this->_model = new Model($this->_conf);
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
|
@ -157,10 +157,10 @@ class ModelTest extends TestCase
|
|||
|
||||
public function testCommentDefaults()
|
||||
{
|
||||
$class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model');
|
||||
$comment = new Comment(
|
||||
$this->_conf,
|
||||
forward_static_call(
|
||||
'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model') . '::getInstance',
|
||||
new $class(
|
||||
$this->_conf->getSection('model_options')
|
||||
)
|
||||
);
|
||||
|
@ -252,7 +252,10 @@ class ModelTest extends TestCase
|
|||
$paste = $model->getPaste();
|
||||
$paste->setData($pasteData);
|
||||
$paste->store();
|
||||
$paste->exists();
|
||||
$this->assertTrue($paste->exists(), 'paste exists before creating comment');
|
||||
|
||||
$comment = $paste->getComment(Helper::getPasteId());
|
||||
$comment->setData($commentData);
|
||||
|
||||
$db = new PDO(
|
||||
$options['model_options']['dsn'],
|
||||
|
@ -264,8 +267,6 @@ class ModelTest extends TestCase
|
|||
$statement->execute();
|
||||
$statement->closeCursor();
|
||||
|
||||
$comment = $paste->getComment(Helper::getPasteId());
|
||||
$comment->setData($commentData);
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionCode(70);
|
||||
$comment->store();
|
||||
|
@ -307,15 +308,8 @@ class ModelTest extends TestCase
|
|||
$comment->get();
|
||||
$comment->store();
|
||||
|
||||
$identicon = new Identicon(array(
|
||||
'hash' => TrafficLimiter::getHash(),
|
||||
'size' => 16,
|
||||
'style' => array(
|
||||
'backgroundColor' => '#fff0', // fully transparent, for dark mode
|
||||
'padding' => 0,
|
||||
),
|
||||
));
|
||||
$pngdata = $identicon->getImageDataUri('png');
|
||||
$identicon = new Identicon();
|
||||
$pngdata = $identicon->getImageDataUri(TrafficLimiter::getHash(), 16);
|
||||
$comment = current($this->_model->getPaste(Helper::getPasteId())->get()['comments']);
|
||||
$this->assertEquals($pngdata, $comment['meta']['icon'], 'icon gets set');
|
||||
}
|
||||
|
@ -428,7 +422,7 @@ class ModelTest extends TestCase
|
|||
public function testPurge()
|
||||
{
|
||||
$conf = new Configuration;
|
||||
$store = Database::getInstance($conf->getSection('model_options'));
|
||||
$store = new Database($conf->getSection('model_options'));
|
||||
$store->delete(Helper::getPasteId());
|
||||
$expired = Helper::getPaste(2, array('expire_date' => 1344803344));
|
||||
$paste = Helper::getPaste(2, array('expire_date' => time() + 3600));
|
||||
|
|
|
@ -16,7 +16,7 @@ class PurgeLimiterTest extends TestCase
|
|||
mkdir($this->_path);
|
||||
}
|
||||
PurgeLimiter::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_path))
|
||||
new Filesystem(array('dir' => $this->_path))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class ServerSaltTest extends TestCase
|
|||
mkdir($this->_path);
|
||||
}
|
||||
ServerSalt::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_path))
|
||||
new Filesystem(array('dir' => $this->_path))
|
||||
);
|
||||
|
||||
$this->_otherPath = $this->_path . DIRECTORY_SEPARATOR . 'foo';
|
||||
|
@ -45,17 +45,17 @@ class ServerSaltTest extends TestCase
|
|||
{
|
||||
// generating new salt
|
||||
ServerSalt::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_path))
|
||||
new Filesystem(array('dir' => $this->_path))
|
||||
);
|
||||
$salt = ServerSalt::get();
|
||||
|
||||
// try setting a different path and resetting it
|
||||
ServerSalt::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_otherPath))
|
||||
new Filesystem(array('dir' => $this->_otherPath))
|
||||
);
|
||||
$this->assertNotEquals($salt, ServerSalt::get());
|
||||
ServerSalt::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_path))
|
||||
new Filesystem(array('dir' => $this->_path))
|
||||
);
|
||||
$this->assertEquals($salt, ServerSalt::get());
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ class ServerSaltTest extends TestCase
|
|||
{
|
||||
// try setting an invalid path
|
||||
chmod($this->_invalidPath, 0000);
|
||||
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
|
||||
$store = new Filesystem(array('dir' => $this->_invalidPath));
|
||||
ServerSalt::setStore($store);
|
||||
$salt = ServerSalt::get();
|
||||
ServerSalt::setStore($store);
|
||||
|
@ -77,7 +77,7 @@ class ServerSaltTest extends TestCase
|
|||
chmod($this->_invalidPath, 0700);
|
||||
file_put_contents($this->_invalidFile, '');
|
||||
chmod($this->_invalidFile, 0000);
|
||||
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
|
||||
$store = new Filesystem(array('dir' => $this->_invalidPath));
|
||||
ServerSalt::setStore($store);
|
||||
$salt = ServerSalt::get();
|
||||
ServerSalt::setStore($store);
|
||||
|
@ -94,7 +94,7 @@ class ServerSaltTest extends TestCase
|
|||
}
|
||||
file_put_contents($this->_invalidPath . DIRECTORY_SEPARATOR . '.htaccess', '');
|
||||
chmod($this->_invalidPath, 0500);
|
||||
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
|
||||
$store = new Filesystem(array('dir' => $this->_invalidPath));
|
||||
ServerSalt::setStore($store);
|
||||
$salt = ServerSalt::get();
|
||||
ServerSalt::setStore($store);
|
||||
|
@ -106,9 +106,9 @@ class ServerSaltTest extends TestCase
|
|||
// try creating an invalid path
|
||||
chmod($this->_invalidPath, 0000);
|
||||
ServerSalt::setStore(
|
||||
Filesystem::getInstance(array('dir' => $this->_invalidPath . DIRECTORY_SEPARATOR . 'baz'))
|
||||
new Filesystem(array('dir' => $this->_invalidPath . DIRECTORY_SEPARATOR . 'baz'))
|
||||
);
|
||||
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
|
||||
$store = new Filesystem(array('dir' => $this->_invalidPath));
|
||||
ServerSalt::setStore($store);
|
||||
$salt = ServerSalt::get();
|
||||
ServerSalt::setStore($store);
|
||||
|
|
|
@ -13,7 +13,7 @@ class TrafficLimiterTest extends TestCase
|
|||
{
|
||||
/* Setup Routine */
|
||||
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'trafficlimit';
|
||||
$store = Filesystem::getInstance(array('dir' => $this->_path));
|
||||
$store = new Filesystem(array('dir' => $this->_path));
|
||||
ServerSalt::setStore($store);
|
||||
TrafficLimiter::setStore($store);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class Vizhash16x16Test extends TestCase
|
|||
mkdir($this->_path);
|
||||
}
|
||||
$this->_file = $this->_path . DIRECTORY_SEPARATOR . 'vizhash.png';
|
||||
ServerSalt::setStore(Filesystem::getInstance(array('dir' => $this->_path)));
|
||||
ServerSalt::setStore(new Filesystem(array('dir' => $this->_path)));
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
</coverage>
|
||||
<testsuite name="PrivateBin Test Suite">
|
||||
<directory suffix=".php">./</directory>
|
||||
<exclude>ConfigurationTestGenerator.php</exclude>
|
||||
</testsuite>
|
||||
<logging>
|
||||
<testdoxHtml outputFile="log/testdox.html"/>
|
||||
|
|
4
vendor/composer/installed.php
vendored
4
vendor/composer/installed.php
vendored
|
@ -5,7 +5,7 @@
|
|||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'reference' => '46013df6201304840c8dab82af63cc9000a6d239',
|
||||
'reference' => 'ba5c859d85244c30711263d61e691217c1bc95e4',
|
||||
'name' => 'privatebin/privatebin',
|
||||
'dev' => false,
|
||||
),
|
||||
|
@ -43,7 +43,7 @@
|
|||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'reference' => '46013df6201304840c8dab82af63cc9000a6d239',
|
||||
'reference' => 'ba5c859d85244c30711263d61e691217c1bc95e4',
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'yzalis/identicon' => array(
|
||||
|
|
Loading…
Reference in New Issue
Block a user