Merge branch 'master' into php8

This commit is contained in:
El RIDO 2022-11-17 06:04:12 +01:00
commit 46c0fc851c
No known key found for this signature in database
GPG key ID: 0F5C940A6BD81F92
44 changed files with 1062 additions and 642 deletions

653
bin/configuration-test-generator Executable file
View file

@ -0,0 +1,653 @@
#!/usr/bin/env php
<?php
/**
* generates a config unit test class
*
* This generator is meant to test all possible configuration combinations
* without having to write endless amounts of code manually.
*
* DANGER: Too many options/settings and too high max iteration setting may trigger
* a fork bomb. Please save your work before executing this script.
*/
define('PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
include PATH . 'tst' . DIRECTORY_SEPARATOR . 'Bootstrap.php';
$vd = array('view', 'delete');
$vcd = array('view', 'create', 'delete');
new ConfigurationTestGenerator(array(
'main/discussion' => array(
array(
'setting' => true,
'tests' => array(
array(
'conditions' => array('steps' => $vd),
'type' => 'RegExp',
'args' => array(
'#<div[^>]*id="opendiscussionoption"[^>]*>#',
'$content',
'outputs enabled discussion correctly',
),
), array(
'conditions' => array('steps' => array('create'), 'traffic/limit' => 10),
'settings' => array('$_POST["opendiscussion"] = "neither 1 nor 0"'),
'type' => 'Equals',
'args' => array(
1,
'$response["status"]',
'when discussions are enabled, but invalid flag posted, fail to create paste',
),
), array(
'conditions' => array('steps' => array('create'), 'traffic/limit' => 10),
'settings' => array('$_POST["opendiscussion"] = "neither 1 nor 0"'),
'type' => 'False',
'args' => array(
'$this->_model->exists(Helper::getPasteId())',
'when discussions are enabled, but invalid flag posted, paste is not created',
),
),
),
'affects' => $vcd,
), array(
'setting' => false,
'tests' => array(
array(
'type' => 'NotRegExp',
'args' => array(
'#<div[^>]*id="opendiscussionoption"[^>]*>#',
'$content',
'outputs disabled discussion correctly',
),
),
),
'affects' => $vd,
),
),
'main/opendiscussion' => array(
array(
'setting' => true,
'tests' => array(
array(
'conditions' => array('main/discussion' => true),
'type' => 'RegExp',
'args' => array(
'#<input[^>]+id="opendiscussion"[^>]*checked="checked"[^>]*>#',
'$content',
'outputs checked discussion correctly',
),
),
),
'affects' => $vd,
), array(
'setting' => false,
'tests' => array(
array(
'conditions' => array('main/discussion' => true),
'type' => 'NotRegExp',
'args' => array(
'#<input[^>]+id="opendiscussion"[^>]*checked="checked"[^>]*>#',
'$content',
'outputs unchecked discussion correctly',
),
),
),
'affects' => $vd,
),
),
'main/burnafterreadingselected' => array(
array(
'setting' => true,
'tests' => array(
array(
'type' => 'RegExp',
'args' => array(
'#<input[^>]+id="burnafterreading"[^>]*checked="checked"[^>]*>#',
'$content',
'preselects burn after reading option',
),
),
),
'affects' => array('view'),
), array(
'setting' => false,
'tests' => array(
array(
'type' => 'NotRegExp',
'args' => array(
'#<input[^>]+id="burnafterreading"[^>]*checked="checked"[^>]*>#',
'$content',
'burn after reading option is unchecked',
),
),
),
'affects' => array('view'),
),
),
'main/password' => array(
array(
'setting' => true,
'tests' => array(
array(
'type' => 'RegExp',
'args' => array(
'#<div[^>]*id="password"[^>]*>#',
'$content',
'outputs password input correctly',
),
),
),
'affects' => $vd,
), array(
'setting' => false,
'tests' => array(
array(
'conditions' => array('main/discussion' => true),
'type' => 'NotRegExp',
'args' => array(
'#<div[^>]*id="password"[^>]*>#',
'$content',
'removes password input correctly',
),
),
),
'affects' => $vd,
),
),
'main/template' => array(
array(
'setting' => 'page',
'tests' => array(
array(
'type' => 'RegExp',
'args' => array(
'#<link[^>]+type="text/css"[^>]+rel="stylesheet"[^>]+href="css/privatebin\.css\\?\d[\d\.]+\d+"[^>]*/>#',
'$content',
'outputs "page" stylesheet correctly',
),
), array(
'type' => 'NotRegExp',
'args' => array(
'#<link[^>]+type="text/css"[^>]+rel="stylesheet"[^>]+href="css/bootstrap/bootstrap-\d[\d\.]+\d\.css"[^>]*/>#',
'$content',
'removes "bootstrap" stylesheet correctly',
),
),
),
'affects' => $vd,
), array(
'setting' => 'bootstrap',
'tests' => array(
array(
'type' => 'NotRegExp',
'args' => array(
'#<link[^>]+type="text/css"[^>]+rel="stylesheet"[^>]+href="css/privatebin\.css\\?\d[\d\.]+\d+"[^>]*/>#',
'$content',
'removes "page" stylesheet correctly',
),
), array(
'type' => 'RegExp',
'args' => array(
'#<link[^>]+type="text/css"[^>]+rel="stylesheet"[^>]+href="css/bootstrap/bootstrap-\d[\d\.]+\d\.css"[^>]*/>#',
'$content',
'outputs "bootstrap" stylesheet correctly',
),
),
),
'affects' => $vd,
),
),
'main/sizelimit' => array(
array(
'setting' => 10,
'tests' => array(
array(
'conditions' => array('steps' => array('create'), 'traffic/limit' => 10),
'type' => 'Equals',
'args' => array(
1,
'$response["status"]',
'when sizelimit limit exceeded, fail to create paste',
),
),
),
'affects' => array('create'),
), array(
'setting' => 2097152,
'tests' => array(
array(
'conditions' => array('steps' => array('create'), 'traffic/limit' => 0, 'main/burnafterreadingselected' => true),
'settings' => array('sleep(3)'),
'type' => 'Equals',
'args' => array(
0,
'$response["status"]',
'when sizelimit limit is not reached, successfully create paste',
),
), array(
'conditions' => array('steps' => array('create'), 'traffic/limit' => 0, 'main/burnafterreadingselected' => true),
'settings' => array('sleep(3)'),
'type' => 'True',
'args' => array(
'$this->_model->exists($response["id"])',
'when sizelimit limit is not reached, paste exists after posting data',
),
),
),
'affects' => array('create'),
),
),
'traffic/limit' => array(
array(
'setting' => 0,
'tests' => array(
array(
'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
'type' => 'Equals',
'args' => array(
0,
'$response["status"]',
'when traffic limit is disabled, successfully create paste',
),
), array(
'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
'type' => 'True',
'args' => array(
'$this->_model->exists($response["id"])',
'when traffic limit is disabled, paste exists after posting data',
),
),
),
'affects' => array('create'),
), array(
'setting' => 10,
'tests' => array(
array(
'conditions' => array('steps' => array('create')),
'type' => 'Equals',
'args' => array(
1,
'$response["status"]',
'when traffic limit is on and we do not wait, fail to create paste',
),
),
),
'affects' => array('create'),
), array(
'setting' => 2,
'tests' => array(
array(
'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
'settings' => array('sleep(3)'),
'type' => 'Equals',
'args' => array(
0,
'$response["status"]',
'when traffic limit is on and we wait, successfully create paste',
),
), array(
'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
'settings' => array('sleep(3)'),
'type' => 'True',
'args' => array(
'$this->_model->exists($response["id"])',
'when traffic limit is on and we wait, paste exists after posting data',
),
),
),
'affects' => array('create'),
),
),
));
class ConfigurationTestGenerator
{
/**
* endless loop protection, since we're working with a recursive function,
* creating factorial configurations
* @var int
*/
const MAX_ITERATIONS = 2000;
/**
* options to test
* @var array
*/
private $_options;
/**
* iteration count to guarantee timely end
* @var int
*/
private $_iterationCount = 0;
/**
* generated configurations
* @var array
*/
private $_configurations = array(
array('options' => array(), 'tests' => array(), 'affects' => array()),
);
/**
* constructor, generates the configuration test
* @param array $options
*/
public function __construct($options)
{
$this->_options = $options;
// generate all possible combinations of options: options^settings
$this->_generateConfigurations();
$this->_writeConfigurationTest();
}
/**
* write configuration test file based on generated configuration array
*/
private function _writeConfigurationTest()
{
$defaultOptions = parse_ini_file(CONF_SAMPLE, true);
$code = $this->_getHeader();
foreach ($this->_configurations as $key => $conf) {
$fullOptions = array_replace_recursive($defaultOptions, $conf['options']);
$options = Helper::varExportMin($fullOptions, true);
foreach ($conf['affects'] as $step) {
$testCode = $preCode = array();
foreach ($conf['tests'] as $tests) {
foreach ($tests[0] as $test) {
// skip if test does not affect this step
if (!in_array($step, $tests[1])) {
continue;
}
// skip if not all test conditions are met
if (array_key_exists('conditions', $test)) {
while (list($path, $setting) = each($test['conditions'])) {
if ($path == 'steps' && !in_array($step, $setting)) {
continue 2;
} elseif ($path != 'steps') {
list($section, $option) = explode('/', $path);
if ($fullOptions[$section][$option] !== $setting) {
continue 2;
}
}
}
}
if (array_key_exists('settings', $test)) {
foreach ($test['settings'] as $setting) {
$preCode[$setting] = $setting;
}
}
$args = array();
foreach ($test['args'] as $arg) {
if (is_string($arg) && strpos($arg, '$') === 0) {
$args[] = $arg;
} else {
$args[] = Helper::varExportMin($arg, true);
}
}
$testCode[] = array($test['type'], implode(', ', $args));
}
}
$code .= $this->_getFunction(
ucfirst($step), $key, $options, $preCode, $testCode, $fullOptions['main']['discussion']
);
}
}
$code .= '}' . PHP_EOL;
file_put_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'tst' . DIRECTORY_SEPARATOR . 'ConfigurationCombinationsTest.php', $code);
}
/**
* get header of configuration test file
*
* @return string
*/
private function _getHeader()
{
return <<<'EOT'
<?php
/**
* DO NOT EDIT: This file is generated automatically using configGenerator.php
*/
use PHPUnit\Framework\TestCase;
use PrivateBin\Controller;
use PrivateBin\Data\Filesystem;
use PrivateBin\Persistence\ServerSalt;
use PrivateBin\Persistence\TrafficLimiter;
use PrivateBin\Request;
class ConfigurationCombinationsTest extends TestCase
{
private $_conf;
private $_model;
private $_path;
public function setUp(): void
{
/* Setup Routine */
Helper::confBackup();
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
$this->_model = new Filesystem(array('dir' => $this->_path));
ServerSalt::setStore($this->_model);
TrafficLimiter::setStore($this->_model);
$this->reset();
}
public function tearDown(): void
{
/* Tear Down Routine */
unlink(CONF);
Helper::confRestore();
Helper::rmDir($this->_path);
}
public function reset($configuration = array())
{
$_POST = array();
$_GET = array();
$_SERVER = array();
if ($this->_model->exists(Helper::getPasteId()))
$this->_model->delete(Helper::getPasteId());
$configuration['model_options']['dir'] = $this->_path;
Helper::createIniFile(CONF, $configuration);
}
EOT;
}
/**
* get unit tests function block
*
* @param string $step
* @param int $key
* @param array $options
* @param array $preCode
* @param array $testCode
* @return string
*/
private function _getFunction($step, $key, &$options, $preCode, $testCode, $discussionEnabled)
{
if (count($testCode) == 0) {
echo "skipping creation of test$step$key, no valid tests found for configuration: $options" . PHP_EOL;
return '';
}
$preString = $testString = '';
foreach ($preCode as $setting) {
$preString .= " $setting;" . PHP_EOL;
}
foreach ($testCode as $test) {
$type = $test[0];
$args = $test[1];
$testString .= " \$this->assert$type($args);" . PHP_EOL;
}
$code = <<<EOT
/**
* @runInSeparateProcess
*/
public function test$step$key()
{
\$this->reset($options);
EOT;
// step specific initialization
switch ($step) {
case 'Create':
if ($discussionEnabled) {
$code .= PHP_EOL . <<<'EOT'
$paste = Helper::getPasteJson();
EOT;
} else {
$code .= PHP_EOL . <<<'EOT'
$paste = json_decode(Helper::getPasteJson(), true);
$paste['adata'][2] = 0;
$paste = json_encode($paste);
EOT;
}
$code .= PHP_EOL . <<<'EOT'
$file = tempnam(sys_get_temp_dir(), 'FOO');
file_put_contents($file, $paste);
Request::setInputStream($file);
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['REMOTE_ADDR'] = '::1';
TrafficLimiter::canPass();
EOT;
break;
case 'Read':
$code .= PHP_EOL . <<<'EOT'
$this->_model->create(Helper::getPasteId(), Helper::getPaste());
$_SERVER['QUERY_STRING'] = Helper::getPasteId();
$_GET[Helper::getPasteId()] = '';
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
EOT;
break;
case 'Delete':
$code .= PHP_EOL . <<<'EOT'
$this->_model->create(Helper::getPasteId(), Helper::getPaste());
$this->assertTrue($this->_model->exists(Helper::getPasteId()), 'paste exists before deleting data');
$_GET['pasteid'] = Helper::getPasteId();
$_GET['deletetoken'] = hash_hmac('sha256', Helper::getPasteId(), $this->_model->read(Helper::getPasteId())['meta']['salt']);
EOT;
break;
}
// all steps
$code .= PHP_EOL . $preString;
$code .= <<<'EOT'
ob_start();
new Controller;
$content = ob_get_contents();
ob_end_clean();
EOT;
// step specific tests
switch ($step) {
case 'Create':
$code .= <<<'EOT'
$response = json_decode($content, true);
EOT;
break;
case 'Read':
$code .= <<<'EOT'
$response = json_decode($content, true);
$this->assertEquals(0, $response['status'], 'outputs success status');
$this->assertEquals(Helper::getPasteId(), $response['id'], 'outputs id correctly');
$this->assertEquals(Helper::getPaste()['data'], $response['data'], 'outputs data correctly');
EOT;
break;
case 'Delete':
$code .= <<<'EOT'
$this->assertMatchesRegularExpression(
'#<div[^>]*id="status"[^>]*>.*Paste was properly deleted[^<]*</div>#s',
$content,
'outputs deleted status correctly'
);
$this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste successfully deleted');
EOT;
break;
}
return $code . PHP_EOL . PHP_EOL . $testString . ' }' . PHP_EOL . PHP_EOL;
}
/**
* recursive function to generate configurations based on options
*
* @throws Exception
* @return array
*/
private function _generateConfigurations()
{
// recursive factorial function
if (++$this->_iterationCount > self::MAX_ITERATIONS) {
echo 'max iterations reached, stopping', PHP_EOL;
return $this->_configurations;
}
echo "generateConfigurations: iteration $this->_iterationCount", PHP_EOL;
$continue = list($path, $settings) = each($this->_options);
if ($continue === false) {
return $this->_configurations;
}
list($section, $option) = explode('/', $path);
for ($c = 0, $max = count($this->_configurations); $c < $max; ++$c) {
if (!array_key_exists($section, $this->_configurations[$c]['options'])) {
$this->_configurations[$c]['options'][$section] = array();
}
if (count($settings) == 0) {
throw new Exception("Check your \$options: option $option has no settings!");
}
// set the first setting in the original configuration
$setting = current($settings);
$this->_addSetting($this->_configurations[$c], $setting, $section, $option);
// create clones for each of the other settings
while ($setting = next($settings)) {
$clone = $this->_configurations[$c];
$this->_configurations[] = $this->_addSetting($clone, $setting, $section, $option);
}
reset($settings);
}
return $this->_generateConfigurations();
}
/**
* add a setting to the given configuration
*
* @param array $configuration
* @param array $setting
* @param string $section
* @param string $option
* @throws Exception
* @return array
*/
private function _addSetting(&$configuration, &$setting, &$section, &$option)
{
if (++$this->_iterationCount > self::MAX_ITERATIONS) {
echo 'max iterations reached, stopping', PHP_EOL;
return $configuration;
}
echo "addSetting: iteration $this->_iterationCount", PHP_EOL;
if (
array_key_exists($option, $configuration['options'][$section]) &&
$configuration['options'][$section][$option] === $setting['setting']
) {
$val = Helper::varExportMin($setting['setting'], true);
throw new Exception("Endless loop or error in options detected: option '$option' already exists with setting '$val' in one of the configurations!");
}
$configuration['options'][$section][$option] = $setting['setting'];
$configuration['tests'][$option] = array($setting['tests'], $setting['affects']);
foreach ($setting['affects'] as $affects) {
if (!in_array($affects, $configuration['affects'])) {
$configuration['affects'][] = $affects;
}
}
return $configuration;
}
}

147
bin/icon-test Executable file
View file

@ -0,0 +1,147 @@
#!/usr/bin/env php
<?php
define('ITERATIONS', 100000);
require dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
use Identicon\Generator\GdGenerator;
use Identicon\Generator\ImageMagickGenerator;
use Identicon\Generator\SvgGenerator;
use Identicon\Identicon;
use Jdenticon\Identicon as Jdenticon;
use PrivateBin\Vizhash16x16;
$vizhash = new Vizhash16x16();
$identiconGenerators = array(
'identicon GD' => new Identicon(new GdGenerator()),
'identicon ImageMagick' => new Identicon(new ImageMagickGenerator()),
'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(
'lengths' => array(),
'time' => 0
),
'identicon GD' => array(
'lengths' => array(),
'time' => 0
),
'identicon ImageMagick' => array(
'lengths' => array(),
'time' => 0
),
'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)));
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) {
$data = 'data:image/png;base64,' . base64_encode(
$vizhash->generate($hmac)
);
$results['vizhash']['lengths'][$i] = strlen($data);
}
$results['vizhash']['time'] = microtime(true) - $start;
foreach ($identiconGenerators as $key => $identicon) {
echo 'run ', $key,' tests', PHP_EOL;
$start = microtime(true);
foreach ($hmacs as $i => $hmac) {
$data = $identicon->getImageDataUri($hmac, 16);
$results[$key]['lengths'][$i] = strlen($data);
}
$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
);
function format_result_line($generator, $min, $max, $avg, $sec) {
echo str_pad($generator, PADDING_LENGTH, ' '), "\t",
str_pad($min, 4, ' ', STR_PAD_LEFT), "\t",
str_pad($max, 4, ' ', STR_PAD_LEFT), "\t",
str_pad($avg, 4, ' ', STR_PAD_LEFT), "\t",
str_pad($sec, 7, ' ', STR_PAD_LEFT), PHP_EOL;
}
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)
);
foreach ($results as $generator => $result) {
sort($result['lengths']);
format_result_line(
$generator . ':',
$result['lengths'][0],
$result['lengths'][ITERATIONS-1],
round(array_sum($result['lengths']) / ITERATIONS),
round($result['time'], 3)
);
}

199
bin/migrate Executable file
View 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();
}