Compare commits

...

9 Commits

Author SHA1 Message Date
Clemens Schwaighofer
a66cc09095 Fix phpstan problems in test db encryption file 2025-04-15 17:46:41 +09:00
Clemens Schwaighofer
1cfdc45107 Fix edit user missing error example for login user id field 2025-04-15 17:40:54 +09:00
Clemens Schwaighofer
07e46c91ab Add test decryption for pg crypto columns 2025-04-14 09:19:58 +09:00
Clemens Schwaighofer
8aee448c59 Update DB IO for query hash storage and parameter count
The parameter count methods in the PgSQL class have changed
- the function returns a unique list of $ parameters

The count is now done in the DB IO part where it counts over the unique array

Query hash is stored like the query for the current run one (reset on dbExec call).
The method to create the hash is renamed to dbBuildQueryHash instead of "Get".
The dbGetQueryHash function now just returns the last set query hash. There is a matching dbResetQueryHash for unsetting the query hash.
2025-04-09 11:35:02 +09:00
Clemens Schwaighofer
37367db878 Fix regex for $$ PostgresSQL string in convert placeholder 2025-04-07 19:44:18 +09:00
Clemens Schwaighofer
2d30d1d160 Rewrite DB param lookup
* Correct wrong comment lookup
* simplify regex by excluding comment and string blocks before
* simpler lookup for each type
* update checks for more tests for various special cases

In DB IO
* add a function to return all placeholders found in a query
* only numbered parameters are looked up
2025-04-07 17:30:30 +09:00
Clemens Schwaighofer
531229e8b7 Add DB Encryption tests 2025-04-07 12:05:06 +09:00
Clemens Schwaighofer
d09c20ff9d hash test page update 2025-04-07 09:09:45 +09:00
Clemens Schwaighofer
f4ddc5a5fc Add hash hmac to the Create Hash class 2025-04-07 09:05:37 +09:00
15 changed files with 664 additions and 164 deletions

View File

@@ -21,8 +21,10 @@ final class CoreLibsCreateHashTest extends TestCase
public function hashData(): array public function hashData(): array
{ {
return [ return [
'any string' => [ 'hash tests' => [
// this is the string
'text' => 'Some String Text', 'text' => 'Some String Text',
// hash list special
'crc32b_reverse' => 'c5c21d91', // crc32b (in revere) 'crc32b_reverse' => 'c5c21d91', // crc32b (in revere)
'sha1Short' => '4d2bc9ba0', // sha1Short 'sha1Short' => '4d2bc9ba0', // sha1Short
// via hash // via hash
@@ -31,6 +33,8 @@ final class CoreLibsCreateHashTest extends TestCase
'fnv132' => '9df444f9', // hash: fnv132 'fnv132' => '9df444f9', // hash: fnv132
'fnv1a32' => '2c5f91b9', // hash: fnv1a32 'fnv1a32' => '2c5f91b9', // hash: fnv1a32
'joaat' => '50dab846', // hash: joaat 'joaat' => '50dab846', // hash: joaat
'ripemd160' => 'aeae3f041b20136451519edd9361570909300342', // hash: ripemd160,
'sha256' => '9055080e022f224fa835929b80582b3c71c672206fa3a49a87412c25d9d42ceb', // hash: sha256
] ]
]; ];
} }
@@ -81,7 +85,7 @@ final class CoreLibsCreateHashTest extends TestCase
{ {
$list = []; $list = [];
foreach ($this->hashData() as $name => $values) { foreach ($this->hashData() as $name => $values) {
foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat'] as $_hash_type) { foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat', 'ripemd160', 'sha256'] as $_hash_type) {
// default value test // default value test
if ($_hash_type === null) { if ($_hash_type === null) {
$hash_type = \CoreLibs\Create\Hash::STANDARD_HASH_SHORT; $hash_type = \CoreLibs\Create\Hash::STANDARD_HASH_SHORT;
@@ -288,7 +292,7 @@ final class CoreLibsCreateHashTest extends TestCase
* Undocumented function * Undocumented function
* *
* @covers ::hash * @covers ::hash
* @testdox hash with invalid type [$_dataName] * @testdox hash with invalid type
* *
* @return void * @return void
*/ */
@@ -301,6 +305,122 @@ final class CoreLibsCreateHashTest extends TestCase
\CoreLibs\Create\Hash::hash($hash_source, 'DOES_NOT_EXIST') \CoreLibs\Create\Hash::hash($hash_source, 'DOES_NOT_EXIST')
); );
} }
/**
* Note: this only tests default sha256
*
* @covers ::hashHmac
* @testdox hash hmac test
*
* @return void
*/
public function testHashMac(): void
{
$hash_key = 'FIX KEY';
$hash_source = 'Some String Text';
$expected = '16479b3ef6fa44e1cdd8b2dcfaadf314d1a7763635e8738f1e7996d714d9b6bf';
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashHmac($hash_source, $hash_key)
);
}
/**
* Undocumented function
*
* @covers ::hashHmac
* @testdox hash hmac with invalid type
*
* @return void
*/
public function testInvalidHashMacType(): void
{
$hash_key = 'FIX KEY';
$hash_source = 'Some String Text';
$expected = hash_hmac(\CoreLibs\Create\Hash::STANDARD_HASH, $hash_source, $hash_key);
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashHmac($hash_source, $hash_key, 'DOES_NOT_EXIST')
);
}
/**
* Undocumented function
*
* @return array<mixed>
*/
public function providerHashTypes(): array
{
return [
'Hash crc32b' => [
'crc32b',
true,
false,
],
'Hash adler32' => [
'adler32',
true,
false,
],
'HAsh fnv132' => [
'fnv132',
true,
false,
],
'Hash fnv1a32' => [
'fnv1a32',
true,
false,
],
'Hash: joaat' => [
'joaat',
true,
false,
],
'Hash: ripemd160' => [
'ripemd160',
true,
true,
],
'Hash: sha256' => [
'sha256',
true,
true,
],
'Hash: invalid' => [
'invalid',
false,
false
]
];
}
/**
* Undocumented function
*
* @covers ::isValidHashType
* @covers ::isValidHashHmacType
* @dataProvider providerHashTypes
* @testdox check if $hash_type is valid for hash $hash_ok and hash hmac $hash_hmac_ok [$_dataName]
*
* @param string $hash_type
* @param bool $hash_ok
* @param bool $hash_hmac_ok
* @return void
*/
public function testIsValidHashAndHashHmacTypes(string $hash_type, bool $hash_ok, bool $hash_hmac_ok): void
{
$this->assertEquals(
$hash_ok,
\CoreLibs\Create\Hash::isValidHashType($hash_type),
'hash valid'
);
$this->assertEquals(
$hash_hmac_ok,
\CoreLibs\Create\Hash::isValidHashHmacType($hash_type),
'hash hmac valid'
);
}
} }
// __END__ // __END__

View File

@@ -135,6 +135,7 @@ final class CoreLibsDBIOTest extends TestCase
} }
// check if they already exist, drop them // check if they already exist, drop them
if ($db->dbShowTableMetaData('table_with_primary_key') !== false) { if ($db->dbShowTableMetaData('table_with_primary_key') !== false) {
$db->dbExec("CREATE EXTENSION IF NOT EXISTS pgcrypto");
$db->dbExec("DROP TABLE table_with_primary_key"); $db->dbExec("DROP TABLE table_with_primary_key");
$db->dbExec("DROP TABLE table_without_primary_key"); $db->dbExec("DROP TABLE table_without_primary_key");
$db->dbExec("DROP TABLE test_meta"); $db->dbExec("DROP TABLE test_meta");
@@ -4744,7 +4745,7 @@ final class CoreLibsDBIOTest extends TestCase
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
// all hast to be string // all hast to be string
foreach ($res as $key => $value) { foreach ($res as $key => $value) {
$this->assertIsString($value, 'Aseert string for column: ' . $key); $this->assertIsString($value, 'Assert string for column: ' . $key);
} }
// convert base only // convert base only
$db->dbSetConvertFlag(Convert::on); $db->dbSetConvertFlag(Convert::on);
@@ -4757,10 +4758,10 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4774,13 +4775,13 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4794,17 +4795,17 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
case 'json': case 'json':
case 'jsonb': case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); $this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4818,25 +4819,25 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
case 'json': case 'json':
case 'jsonb': case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); $this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name);
break; break;
case 'bytea': case 'bytea':
// for hex types it must not start with \x // for hex types it must not start with \x
$this->assertStringStartsNotWith( $this->assertStringStartsNotWith(
'\x', '\x',
$value, $value,
'Aseert bytes not starts with \x for column: ' . $key . '/' . $name 'Assert bytes not starts with \x for column: ' . $key . '/' . $name
); );
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -5008,8 +5009,8 @@ final class CoreLibsDBIOTest extends TestCase
) )
), ),
($params === null ? ($params === null ?
$db->dbGetQueryHash($query) : $db->dbBuildQueryHash($query) :
$db->dbGetQueryHash($query, $params) $db->dbBuildQueryHash($query, $params)
), ),
'Failed assertdbGetQueryHash ' 'Failed assertdbGetQueryHash '
); );
@@ -5235,6 +5236,9 @@ final class CoreLibsDBIOTest extends TestCase
$3 $3
-- comment 3 -- comment 3
, $4 , $4
-- ignore $5, $6
-- $7, $8
-- digest($9, 10)
) )
SQL, SQL,
'count' => 4, 'count' => 4,
@@ -5305,8 +5309,57 @@ final class CoreLibsDBIOTest extends TestCase
SQL, SQL,
'count' => 2, 'count' => 2,
'convert' => false, 'convert' => false,
],
// special $$ string case
'text string, with $ placehoders that could be seen as $$ string' => [
'query' => <<<SQL
SELECT row_int
FROM table_with_primary_key
WHERE
row_bytea = digest($3::VARCHAR, $4) OR
row_varchar = encode(digest($3, $4), 'hex') OR
row_bytea = hmac($3, $5, $4) OR
row_varchar = encode(hmac($3, $5, $4), 'hex') OR
row_bytea = pgp_sym_encrypt($3, $6) OR
row_varchar = encode(pgp_sym_encrypt($1, $6), 'hex') OR
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
SQL,
'count' => 6,
'convert' => false,
],
// NOTE, in SQL heredoc we cannot write $$ strings parts
'text string, with $ placehoders are in $$ strings' => [
'query' => '
SELECT row_int
FROM table_with_primary_key
WHERE
row_varchar = $$some string$$ OR
row_varchar = $tag$some string$tag$ OR
row_varchar = $btag$some $1 string$btag$ OR
row_varchar = $btag$some $1 $subtag$ something $subtag$string$btag$ OR
row_varchar = $1
',
'count' => 1,
'convert' => false,
],
// a text string with escaped quite
'text string, with escaped quote' => [
'query' => <<<SQL
SELECT row_int
FROM table_with_primary_key
WHERE
row_varchar = 'foo bar bar baz $5' OR
row_varchar = 'foo bar '' barbar $6' OR
row_varchar = E'foo bar \' barbar $7' OR
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
SQL,
'count' => 2,
'convert' => false,
] ]
]; ];
$string = <<<SQL
'''
SQL;
} }
/** /**

View File

@@ -21,6 +21,7 @@ ob_end_flush();
use CoreLibs\Debug\Support; use CoreLibs\Debug\Support;
use CoreLibs\DB\Support\ConvertPlaceholder; use CoreLibs\DB\Support\ConvertPlaceholder;
use CoreLibs\Convert\Html;
$log = new CoreLibs\Logging\Logging([ $log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG, 'log_folder' => BASE . LOG,
@@ -38,10 +39,12 @@ print '<div><h1>' . $PAGE_NAME . '</h1></div>';
print "LOGFILE NAME: " . $log->getLogFile() . "<br>"; print "LOGFILE NAME: " . $log->getLogFile() . "<br>";
print "LOGFILE ID: " . $log->getLogFileId() . "<br>"; print "LOGFILE ID: " . $log->getLogFileId() . "<br>";
print "Lookup Regex: <pre>" . ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS . "</pre>"; print "Lookup Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS) . "</pre>";
print "Replace Named Regex: <pre>" . ConvertPlaceholder::REGEX_REPLACE_NAMED . "</pre>"; print "Lookup Numbered Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_LOOKUP_NUMBERED) . "</pre>";
print "Replace Named Regex: <pre>" . ConvertPlaceholder::REGEX_REPLACE_QUESTION_MARK . "</pre>"; print "Replace Named Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_NAMED) . "</pre>";
print "Replace Named Regex: <pre>" . ConvertPlaceholder::REGEX_REPLACE_NUMBERED . "</pre>"; print "Replace Question Mark Regex: <pre>"
. Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_QUESTION_MARK) . "</pre>";
print "Replace Numbered Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_NUMBERED) . "</pre>";
$uniqid = \CoreLibs\Create\Uids::uniqIdShort(); $uniqid = \CoreLibs\Create\Uids::uniqIdShort();
// $binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: ''); // $binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: '');
@@ -91,40 +94,63 @@ RETURNING
some_binary some_binary
SQL; SQL;
print "[ALL] Convert: " print "<b>[ALL] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez"; $query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez";
$params = [':baz' => 'SETBAZ', ':bez' => 'SETBEZ', ':biz' => 'SETBIZ']; $params = [':baz' => 'SETBAZ', ':bez' => 'SETBEZ', ':biz' => 'SETBIZ'];
print "[NO PARAMS] Convert: " print "<b>[NO PARAMS] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez"; $query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez";
$params = null; $params = null;
print "[NO PARAMS] Convert: " print "<b>[NO PARAMS] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> :row_varchar"; $query = "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> :row_varchar";
$params = null; $params = null;
print "[NO PARAMS] Convert: " print "<b>[NO PARAMS] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT row_varchar, row_varchar_literal, row_int, row_date FROM table_with_primary_key"; $query = "SELECT row_varchar, row_varchar_literal, row_int, row_date FROM table_with_primary_key";
$params = null; $params = null;
print "[NO PARAMS] TEST: " print "<b>[NO PARAMS] TEST</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
print "[P-CONV]: " $query = <<<SQL
UPDATE table_with_primary_key SET
row_int = $1::INT, row_numeric = $1::NUMERIC, row_varchar = $1
WHERE
row_varchar = $1
SQL;
$params = [1];
print "<b>[All the same params] TEST</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>";
echo "<hr>";
$query = <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = :row_varchar
SQL;
$params = [':row_varchar' => 1];
print "<b>[: param] TEST</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>";
echo "<hr>";
print "<b>[P-CONV]</b>: "
. Support::printAr( . Support::printAr(
ConvertPlaceholder::updateParamList([ ConvertPlaceholder::updateParamList([
'original' => [ 'original' => [
@@ -186,6 +212,13 @@ SQL,
'params' => [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234], 'params' => [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234],
'direction' => 'pg', 'direction' => 'pg',
], ],
'b?' => [
'query' => <<<SQL
SELECT test FROM test_foo = ?
SQL,
'params' => [1234],
'direction' => 'pg',
],
'b:' => [ 'b:' => [
'query' => <<<SQL 'query' => <<<SQL
INSERT INTO test_foo ( INSERT INTO test_foo (
@@ -220,7 +253,7 @@ foreach ($test_queries as $info => $data) {
$query = $data['query']; $query = $data['query'];
$params = $data['params']; $params = $data['params'];
$direction = $data['direction']; $direction = $data['direction'];
print "[$info] Convert: " print "<b>[$info] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params, $direction)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params, $direction))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";

View File

@@ -0,0 +1,166 @@
<?php // phpcs:ignore warning
/**
* @phan-file-suppress PhanTypeSuspiciousStringExpression
*/
declare(strict_types=1);
// turn on all error reporting
error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR);
ob_start();
// basic class test file
define('USE_DATABASE', true);
// sample config
require 'config.php';
// for testing encryption compare
use OpenPGP\OpenPGP;
// define log file id
$LOG_FILE_ID = 'classTest-db-query-encryption';
ob_end_flush();
// use CoreLibs\Debug\Support;
use CoreLibs\Security\SymmetricEncryption;
use CoreLibs\Security\CreateKey;
use CoreLibs\Create\Hash;
use CoreLibs\Debug\Support;
$log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG,
'log_file_id' => $LOG_FILE_ID,
'log_per_date' => true,
]);
// db connection and attach logger
$db = new CoreLibs\DB\IO(DB_CONFIG, $log);
$db->log->debug('START', '=============================>');
$PAGE_NAME = 'TEST CLASS: DB QUERY ENCRYPTION';
print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title></head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
// encryption key
$key_new = CreateKey::generateRandomKey();
print "Secret Key NEW: " . $key_new . "<br>";
// for reproducable test results
$key = 'e475c19b9a3c8363feb06b51f5b73f1dc9b6f20757d4ab89509bf5cc70ed30ec';
print "Secret Key: " . $key . "<br>";
// test text
$text_string = "I a some deep secret";
$text_string = "I a some deep secret ABC";
//
$crypt = new SymmetricEncryption($key);
$encrypted = $crypt->encrypt($text_string);
$string_hashed = Hash::hashStd($text_string);
$string_hmac = Hash::hashHmac($text_string, $key);
$decrypted = $crypt->decrypt($encrypted);
print "String: " . $text_string . "<br>";
print "Encrypted: " . $encrypted . "<br>";
print "Hashed: " . $string_hashed . "<br>";
print "Hmac: " . $string_hmac . "<br>";
$db->dbExecParams(
<<<SQL
INSERT INTO test_encryption (
-- for compare
plain_text,
-- via php encryption
hash_text, hmac_text, crypt_text,
-- -- in DB encryption
pg_digest_bytea, pg_digest_text,
pg_hmac_bytea, pg_hmac_text,
pg_crypt_bytea, pg_crypt_text
) VALUES (
$1,
$2, $3, $4,
digest($1::VARCHAR, $5),
encode(digest($1, $5), 'hex'),
hmac($1, $6, $5),
encode(hmac($1, $6, $5), 'hex'),
pgp_sym_encrypt($1, $7),
encode(pgp_sym_encrypt($1, $7), 'hex')
) RETURNING cuuid
SQL,
[
// 1: original string
$text_string,
// 2: hashed, 3: hmac, 4: encrypted
$string_hashed, $string_hmac, $encrypted,
// 5: hash type, 6: hmac secret, 7: pgp secret
'sha256', $key, $key
]
);
$cuuid = $db->dbGetReturningExt('cuuid');
print "INSERTED: " . print_r($cuuid, true) . "<br>";
print "LAST ERROR: " . $db->dbGetLastError(true) . "<br>";
// read back
$res = $db->dbReturnRowParams(
<<<SQL
SELECT
-- for compare
plain_text,
-- via php encryption
hash_text, hmac_text, crypt_text,
-- in DB encryption
pg_digest_bytea, pg_digest_text,
pg_hmac_bytea, pg_hmac_text,
pg_crypt_bytea, pg_crypt_text,
encode(pg_crypt_bytea, 'hex') AS pg_crypt_bytea_hex,
pgp_sym_decrypt(pg_crypt_bytea, $2) AS from_pg_crypt_bytea,
pgp_sym_decrypt(decode(pg_crypt_text, 'hex'), $2) AS from_pg_crypt_text
FROM
test_encryption
WHERE
cuuid = $1
SQL,
[
$cuuid, $key
]
);
print "RES: <pre>" . Support::prAr($res) . "</pre><br>";
if ($res === false) {
echo "Failed to run query<br>";
} else {
if (hash_equals($string_hashed, $res['pg_digest_text'])) {
print "libsodium and pgcrypto hash match<br>";
}
if (hash_equals($string_hmac, $res['pg_hmac_text'])) {
print "libsodium and pgcrypto hash hmac match<br>";
}
// do compare for PHP and pgcrypto settings
$encryptedMessage_template = <<<TEXT
-----BEGIN PGP MESSAGE-----
{BASE64}
-----END PGP MESSAGE-----
TEXT;
$base64_string = base64_encode(hex2bin($res['pg_crypt_text']) ?: '');
$encryptedMessage = str_replace(
'{BASE64}',
$base64_string,
$encryptedMessage_template
);
try {
$literalMessage = OpenPGP::decryptMessage($encryptedMessage, passwords: [$key]);
$decrypted = $literalMessage->getLiteralData()->getData();
print "Pg decrypted PHP: " . $decrypted . "<br>";
if ($decrypted == $text_string) {
print "Decryption worked<br>";
}
} catch (\Exception $e) {
print "Error decrypting message: " . $e->getMessage() . "<br>";
}
}
print "</body></html>";
// __END__

View File

@@ -54,7 +54,7 @@ if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) {
print "NO DB HANDLER<br>"; print "NO DB HANDLER<br>";
} }
// REGEX for placeholder count // REGEX for placeholder count
print "Placeholder regex: <pre>" . CoreLibs\DB\Support\ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS . "</pre>"; print "Placeholder lookup regex: <pre>" . CoreLibs\DB\Support\ConvertPlaceholder::REGEX_LOOKUP_NUMBERED . "</pre>";
// turn on debug replace for placeholders // turn on debug replace for placeholders
$db->dbSetDebugReplacePlaceholder(true); $db->dbSetDebugReplacePlaceholder(true);
@@ -148,6 +148,7 @@ RETURNING
bigint_a, number_real, number_double, numeric_3, bigint_a, number_real, number_double, numeric_3,
uuid_var uuid_var
SQL; SQL;
print "Placeholders: <pre>" . print_r($db->dbGetQueryParamPlaceholders($query_insert), true) . "<pre>";
$status = $db->dbExecParams($query_insert, $query_params); $status = $db->dbExecParams($query_insert, $query_params);
echo "<b>*</b><br>"; echo "<b>*</b><br>";
echo "INSERT ALL COLUMN TYPES: " echo "INSERT ALL COLUMN TYPES: "
@@ -326,6 +327,7 @@ SQL,
) { ) {
print "RES: " . Support::prAr($res) . "<br>"; print "RES: " . Support::prAr($res) . "<br>";
} }
print "PL: " . Support::PrAr($db->dbGetPlaceholderConverted()) . "<br>";
print "ERROR: " . $db->dbGetLastError(true) . "<br>"; print "ERROR: " . $db->dbGetLastError(true) . "<br>";
print "</body></html>"; print "</body></html>";

View File

@@ -19,6 +19,7 @@ $LOG_FILE_ID = 'classTest-hash';
ob_end_flush(); ob_end_flush();
use CoreLibs\Create\Hash; use CoreLibs\Create\Hash;
use CoreLibs\Security\CreateKey;
$log = new CoreLibs\Logging\Logging([ $log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG, 'log_folder' => BASE . LOG,
@@ -43,16 +44,24 @@ print "S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "<br>";
print "S::hashShort(__sha1Short replace): $to_crc: " . Hash::hashShort($to_crc) . "<br>"; print "S::hashShort(__sha1Short replace): $to_crc: " . Hash::hashShort($to_crc) . "<br>";
// print "S::__SHA1SHORT(on): $to_crc: " . Hash::__sha1short($to_crc, true) . "<br>"; // print "S::__SHA1SHORT(on): $to_crc: " . Hash::__sha1short($to_crc, true) . "<br>";
print "S::sha1Short(__sha1Short replace): $to_crc: " . Hash::sha1Short($to_crc) . "<br>"; print "S::sha1Short(__sha1Short replace): $to_crc: " . Hash::sha1Short($to_crc) . "<br>";
print "S::__hash(d): " . $to_crc . "/" // print "S::__hash(d): " . $to_crc . "/"
. Hash::STANDARD_HASH_SHORT . ": " . $hash_class::__hash($to_crc) . "<br>"; // . Hash::STANDARD_HASH_SHORT . ": " . $hash_class::__hash($to_crc) . "<br>";
foreach (['adler32', 'fnv132', 'fnv1a32', 'joaat', 'sha512'] as $__hash_c) { $to_crc_list = [
print "S::__hash($__hash_c): $to_crc: " . $hash_class::__hash($to_crc, $__hash_c) . "<br>"; 'Some text block',
'Some String Text',
'any string',
];
foreach ($to_crc_list as $__to_crc) {
foreach (['adler32', 'fnv132', 'fnv1a32', 'joaat', 'ripemd160', 'sha256', 'sha512'] as $__hash_c) {
print "Hash::hash($__hash_c): $__to_crc: " . Hash::hash($to_crc, $__hash_c) . "<br>";
}
} }
// static use // static use
print "U-S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "<br>"; print "U-S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "<br>";
echo "<hr>"; echo "<hr>";
$text = 'Some String Text'; $text = 'Some String Text';
// $text = 'any string';
$type = 'crc32b'; $type = 'crc32b';
print "Hash: " . $type . ": " . hash($type, $text) . "<br>"; print "Hash: " . $type . ": " . hash($type, $text) . "<br>";
// print "Class (old): " . $type . ": " . Hash::__hash($text, $type) . "<br>"; // print "Class (old): " . $type . ": " . Hash::__hash($text, $type) . "<br>";
@@ -66,6 +75,31 @@ print "HASH SHORT: " . $to_crc . ": " . Hash::hashShort($to_crc) . "<br>";
print "HASH LONG: " . $to_crc . ": " . Hash::hashLong($to_crc) . "<br>"; print "HASH LONG: " . $to_crc . ": " . Hash::hashLong($to_crc) . "<br>";
print "HASH DEFAULT: " . $to_crc . ": " . Hash::hashStd($to_crc) . "<br>"; print "HASH DEFAULT: " . $to_crc . ": " . Hash::hashStd($to_crc) . "<br>";
echo "<hr>";
$key = CreateKey::generateRandomKey();
$key = "FIX KEY";
print "Secret Key: " . $key . "<br>";
print "HASHMAC DEFAULT (fix): " . $to_crc . ": " . Hash::hashHmac($to_crc, $key) . "<br>";
$key = CreateKey::generateRandomKey();
print "Secret Key: " . $key . "<br>";
print "HASHMAC DEFAULT (random): " . $to_crc . ": " . Hash::hashHmac($to_crc, $key) . "<br>";
echo "<hr>";
$hash_types = ['crc32b', 'sha256', 'invalid'];
foreach ($hash_types as $hash_type) {
echo "<b>Checking $hash_type:</b><br>";
if (Hash::isValidHashType($hash_type)) {
echo "hash type: $hash_type is valid<br>";
} else {
echo "hash type: $hash_type is INVALID<br>";
}
if (Hash::isValidHashHmacType($hash_type)) {
echo "hash hmac type: $hash_type is valid<br>";
} else {
echo "hash hmac type: $hash_type is INVALID<br>";
}
}
// print "UNIQU ID SHORT : " . Hash::__uniqId() . "<br>"; // print "UNIQU ID SHORT : " . Hash::__uniqId() . "<br>";
// print "UNIQU ID LONG : " . Hash::__uniqIdLong() . "<br>"; // print "UNIQU ID LONG : " . Hash::__uniqIdLong() . "<br>";

View File

@@ -95,6 +95,7 @@ $test_files = [
'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn', 'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn',
'class_test.db.single.php' => 'Class Test: DB single query tests', 'class_test.db.single.php' => 'Class Test: DB single query tests',
'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder', 'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder',
'class_test.db.encryption.php' => 'Class Test: DB pgcrypto',
'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS', 'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS',
'class_test.check.colors.php' => 'Class Test: CHECK COLORS', 'class_test.check.colors.php' => 'Class Test: CHECK COLORS',
'class_test.mime.php' => 'Class Test: MIME', 'class_test.mime.php' => 'Class Test: MIME',

View File

@@ -24,6 +24,7 @@
"egrajp/smarty-extended": "^5.4", "egrajp/smarty-extended": "^5.4",
"php": ">=8.1", "php": ">=8.1",
"gullevek/dotenv": "^2.0", "gullevek/dotenv": "^2.0",
"psr/log": "^2.0 || ^3.0" "psr/log": "^2.0 || ^3.0",
"php-privacy/openpgp": "^2.1"
} }
} }

View File

@@ -49,7 +49,7 @@ class Hash
* replacement for __crc32b call * replacement for __crc32b call
* *
* @param string $string string to hash * @param string $string string to hash
* @param bool $use_sha use sha1 instead of crc32b (default false) * @param bool $use_sha [default=false] use sha1 instead of crc32b
* @return string hash of the string * @return string hash of the string
* @deprecated use __crc32b() for drop in replacement with default, or sha1Short() for use sha true * @deprecated use __crc32b() for drop in replacement with default, or sha1Short() for use sha true
*/ */
@@ -81,7 +81,7 @@ class Hash
* all that create 8 char long hashes * all that create 8 char long hashes
* *
* @param string $string string to hash * @param string $string string to hash
* @param string $hash_type hash type (default adler32) * @param string $hash_type [default=STANDARD_HASH_SHORT] hash type (default adler32)
* @return string hash of the string * @return string hash of the string
* @deprecated use hashShort() of short hashes with adler 32 or hash() for other hash types * @deprecated use hashShort() of short hashes with adler 32 or hash() for other hash types
*/ */
@@ -92,12 +92,40 @@ class Hash
return self::hash($string, $hash_type); return self::hash($string, $hash_type);
} }
/**
* check if hash type is valid, returns false if not
*
* @param string $hash_type
* @return bool
*/
public static function isValidHashType(string $hash_type): bool
{
if (!in_array($hash_type, hash_algos())) {
return false;
}
return true;
}
/**
* check if hash hmac type is valid, returns false if not
*
* @param string $hash_hmac_type
* @return bool
*/
public static function isValidHashHmacType(string $hash_hmac_type): bool
{
if (!in_array($hash_hmac_type, hash_hmac_algos())) {
return false;
}
return true;
}
/** /**
* creates a hash over string if any valid hash given. * creates a hash over string if any valid hash given.
* if no hash type set use sha256 * if no hash type set use sha256
* *
* @param string $string string to ash * @param string $string string to hash
* @param string $hash_type hash type (default sha256) * @param string $hash_type [default=STANDARD_HASH] hash type (default sha256)
* @return string hash of the string * @return string hash of the string
*/ */
public static function hash( public static function hash(
@@ -108,12 +136,36 @@ class Hash
empty($hash_type) || empty($hash_type) ||
!in_array($hash_type, hash_algos()) !in_array($hash_type, hash_algos())
) { ) {
// fallback to default hash type if none set or invalid // fallback to default hash type if empty or invalid
$hash_type = self::STANDARD_HASH; $hash_type = self::STANDARD_HASH;
} }
return hash($hash_type, $string); return hash($hash_type, $string);
} }
/**
* creates a hash mac key
*
* @param string $string string to hash mac
* @param string $key key to use
* @param string $hash_type [default=STANDARD_HASH]
* @return string hash mac string
*/
public static function hashHmac(
string $string,
#[\SensitiveParameter]
string $key,
string $hash_type = self::STANDARD_HASH
): string {
if (
empty($hash_type) ||
!in_array($hash_type, hash_hmac_algos())
) {
// fallback to default hash type if e or invalid
$hash_type = self::STANDARD_HASH;
}
return hash_hmac($hash_type, $string, $key);
}
/** /**
* short hash with max length of 8, uses adler32 * short hash with max length of 8, uses adler32
* *

View File

@@ -303,6 +303,8 @@ class IO
private string $query = ''; private string $query = '';
/** @var array<mixed> current params for query */ /** @var array<mixed> current params for query */
private array $params = []; private array $params = [];
/** @var string current hash build from query and params */
private string $query_hash = '';
// if we do have a convert call, store the convert data in here, else it will be empty // if we do have a convert call, store the convert data in here, else it will be empty
/** @var array{}|array{original:array{query:string,params:array<mixed>},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>} */ /** @var array{}|array{original:array{query:string,params:array<mixed>},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>} */
private array $placeholder_converted = []; private array $placeholder_converted = [];
@@ -1319,7 +1321,7 @@ class IO
*/ */
private function __dbCountQueryParams(string $query): int private function __dbCountQueryParams(string $query): int
{ {
return $this->db_functions->__dbCountQueryParams($query); return count($this->db_functions->__dbGetQueryParams($query));
} }
/** /**
@@ -1382,6 +1384,8 @@ class IO
$this->query = $query; $this->query = $query;
// current params // current params
$this->params = $params; $this->params = $params;
// empty on new
$this->query_hash = '';
// no query set // no query set
if (empty($this->query)) { if (empty($this->query)) {
$this->__dbError(11); $this->__dbError(11);
@@ -1441,7 +1445,7 @@ class IO
$this->returning_id = true; $this->returning_id = true;
} }
// import protection, hash needed // import protection, hash needed
$query_hash = $this->dbGetQueryHash($this->query, $this->params); $query_hash = $this->dbBuildQueryHash($this->query, $this->params);
// QUERY PARAMS: run query params check and rewrite // QUERY PARAMS: run query params check and rewrite
if ($this->dbGetConvertPlaceholder() === true) { if ($this->dbGetConvertPlaceholder() === true) {
try { try {
@@ -1475,7 +1479,8 @@ class IO
return false; return false;
} }
} }
// set query hash
$this->query_hash = $query_hash;
// $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id); // $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id);
// for DEBUG, only on first time ;) // for DEBUG, only on first time ;)
$this->__dbDebug( $this->__dbDebug(
@@ -1959,7 +1964,7 @@ class IO
{ {
// set start array // set start array
if ($query) { if ($query) {
$array = $this->cursor_ext[$this->dbGetQueryHash($query)] ?? []; $array = $this->cursor_ext[$this->dbBuildQueryHash($query)] ?? [];
} else { } else {
$array = $this->cursor_ext; $array = $this->cursor_ext;
} }
@@ -2361,7 +2366,7 @@ class IO
return false; return false;
} }
// create hash from query ... // create hash from query ...
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
// pre declare array // pre declare array
if (!isset($this->cursor_ext[$query_hash])) { if (!isset($this->cursor_ext[$query_hash])) {
$this->cursor_ext[$query_hash] = [ $this->cursor_ext[$query_hash] = [
@@ -2940,7 +2945,7 @@ class IO
public function dbCacheReset(string $query, array $params = []): bool public function dbCacheReset(string $query, array $params = []): bool
{ {
$this->__dbErrorReset(); $this->__dbErrorReset();
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
// clears cache for this query // clears cache for this query
if (empty($this->cursor_ext[$query_hash]['query'])) { if (empty($this->cursor_ext[$query_hash]['query'])) {
$this->__dbWarning(18, context: [ $this->__dbWarning(18, context: [
@@ -2982,7 +2987,7 @@ class IO
if ($query === null) { if ($query === null) {
return $this->cursor_ext; return $this->cursor_ext;
} }
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if ( if (
!empty($this->cursor_ext) && !empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash]) isset($this->cursor_ext[$query_hash])
@@ -3012,7 +3017,7 @@ class IO
$this->__dbError(11); $this->__dbError(11);
return false; return false;
} }
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if ( if (
!empty($this->cursor_ext) && !empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash]) isset($this->cursor_ext[$query_hash])
@@ -3038,7 +3043,7 @@ class IO
$this->__dbError(11); $this->__dbError(11);
return false; return false;
} }
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if ( if (
!empty($this->cursor_ext) && !empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash]) isset($this->cursor_ext[$query_hash])
@@ -3064,7 +3069,7 @@ class IO
*/ */
public function dbResetQueryCalled(string $query, array $params = []): void public function dbResetQueryCalled(string $query, array $params = []): void
{ {
$this->query_called[$this->dbGetQueryHash($query, $params)] = 0; $this->query_called[$this->dbBuildQueryHash($query, $params)] = 0;
} }
/** /**
@@ -3077,7 +3082,7 @@ class IO
*/ */
public function dbGetQueryCalled(string $query, array $params = []): int public function dbGetQueryCalled(string $query, array $params = []): int
{ {
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if (!empty($this->query_called[$query_hash])) { if (!empty($this->query_called[$query_hash])) {
return $this->query_called[$query_hash]; return $this->query_called[$query_hash];
} else { } else {
@@ -4046,7 +4051,7 @@ class IO
} }
/** /**
* Returns hash for query * Creates hash for query and parameters
* Hash is used in all internal storage systems for return data * Hash is used in all internal storage systems for return data
* *
* @param string $query The query to create the hash from * @param string $query The query to create the hash from
@@ -4054,7 +4059,7 @@ class IO
* data to create a unique call one, optional * data to create a unique call one, optional
* @return string Hash, as set by hash long * @return string Hash, as set by hash long
*/ */
public function dbGetQueryHash(string $query, array $params = []): string public function dbBuildQueryHash(string $query, array $params = []): string
{ {
return Hash::hashLong( return Hash::hashLong(
$query . ( $query . (
@@ -4104,6 +4109,26 @@ class IO
$this->params = []; $this->params = [];
} }
/**
* get the current set query hash
*
* @return string Current Query hash
*/
public function dbGetQueryHash(): string
{
return $this->query_hash;
}
/**
* reset query hash
*
* @return void
*/
public function dbResetQueryHash(): void
{
$this->query_hash = '';
}
/** /**
* Returns the placeholder convert set or empty * Returns the placeholder convert set or empty
* *
@@ -4283,6 +4308,17 @@ class IO
return $this->field_names[$pos] ?? false; return $this->field_names[$pos] ?? false;
} }
/**
* get all the $ placeholders
*
* @param string $query
* @return array<string>
*/
public function dbGetQueryParamPlaceholders(string $query): array
{
return $this->db_functions->__dbGetQueryParams($query);
}
/** /**
* Return a field type for a field name or pos, * Return a field type for a field name or pos,
* will return false if field is not found in list * will return false if field is not found in list

View File

@@ -379,9 +379,9 @@ interface SqlFunctions
* Undocumented function * Undocumented function
* *
* @param string $query * @param string $query
* @return int * @return array<string>
*/ */
public function __dbCountQueryParams(string $query): int; public function __dbGetQueryParams(string $query): array;
} }
// __END__ // __END__

View File

@@ -978,12 +978,12 @@ class PgSQL implements Interface\SqlFunctions
} }
/** /**
* Count placeholder queries. $ only * Get the all the $ params, as a unique list
* *
* @param string $query * @param string $query
* @return int * @return array<string>
*/ */
public function __dbCountQueryParams(string $query): int public function __dbGetQueryParams(string $query): array
{ {
$matches = []; $matches = [];
// regex for params: only stand alone $number allowed // regex for params: only stand alone $number allowed
@@ -998,11 +998,11 @@ class PgSQL implements Interface\SqlFunctions
// Matches in 1:, must be array_filtered to remove empty, count with array_unique // Matches in 1:, must be array_filtered to remove empty, count with array_unique
// Regex located in the ConvertPlaceholder class // Regex located in the ConvertPlaceholder class
preg_match_all( preg_match_all(
ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS, ConvertPlaceholder::REGEX_LOOKUP_NUMBERED,
$query, $query,
$matches $matches
); );
return count(array_unique(array_filter($matches[3]))); return array_unique(array_filter($matches[ConvertPlaceholder::MATCHING_POS]));
} }
} }

View File

@@ -14,76 +14,57 @@ namespace CoreLibs\DB\Support;
class ConvertPlaceholder class ConvertPlaceholder
{ {
// NOTE for missing: range */+ are not iplemented in the regex below, but - is for now /** @var string text block in SQL, single quited
// NOTE some combinations are allowed, but the query will fail before this * Note that does not include $$..$$ strings or anything with token name or nested ones
/** @var string split regex, entries before $ group */ */
private const PATTERN_QUERY_SPLIT = private const PATTERN_TEXT_BLOCK_SINGLE_QUOTE = '(?:\'(?:[^\'\\\\]|\\\\.)*\')';
'\?\?|' // UNKNOWN: double ??, is this to avoid something? /** @var string text block in SQL, dollar quoted
. '[\(,]|' // for ',' and '(' mostly in INSERT or ANY() * NOTE: if this is added everything shifts by one lookup number
. '[<>=]|' // general set for <, >, = in any query with any combination */
. '\^@|' // text search for start from text with ^@ private const PATTERN_TEXT_BLOCK_DOLLAR = '(?:\$(\w*)\$.*?\$\1\$)';
. '\|\||' // concats two elements
. '&&|' // array overlap
. '\-\|\-|' // range overlap for array
. '[^-]-{1}|' // single -, used in JSON too
. '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-|' // JSON searches, Array searchs, etc
. 'THEN|ELSE' // command parts (CASE)
;
/** @var string the main regex including the pattern query split */
private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:' . self::PATTERN_QUERY_SPLIT . ')\s*';
/** @var string comment regex /** @var string comment regex
* anything that starts with -- and ends with a line break but any character that is not line break inbetween */ * anything that starts with -- and ends with a line break but any character that is not line break inbetween
private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)*\s*'; * this is the FIRST thing in the line and will skip any further lookups */
/** @var string parts to ignore in the SQL */ private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)';
private const PATTERN_IGNORE = // below are the params lookups
// digit -> ignore /** @var string named parameters, must start with single : */
'\d+|' private const PATTERN_NAMED = '((?<!:):(?:\w+))';
// other string -> ignore /** @var string question mark parameters, will catch any */
. '(?:\'.*?\')|'; private const PATTERN_QUESTION_MARK = '(\?{1})';
/** @var string named parameters */ /** @var string numbered parameters, can only start 1 to 9, second and further digits can be 0-9
private const PATTERN_NAMED = '(:\w+)'; * This ignores the $$ ... $$ escape syntax. If we find something like this will fail
/** @var string question mark parameters */ * It is recommended to use proper string escape quiting for writing data to the DB
private const PATTERN_QUESTION_MARK = '(?:(?:\?\?)?\s*(\?{1}))'; */
/** @var string numbered parameters */
private const PATTERN_NUMBERED = '(\$[1-9]{1}(?:[0-9]{1,})?)'; private const PATTERN_NUMBERED = '(\$[1-9]{1}(?:[0-9]{1,})?)';
// below here are full regex that will be used // below here are full regex that will be used
/** @var string replace regex for named (:...) entries */ /** @var string replace regex for named (:...) entries */
public const REGEX_REPLACE_NAMED = '/' public const REGEX_REPLACE_NAMED = '/'
. '(' . self::PATTERN_ELEMENT . ')' . self::PATTERN_COMMENT . '|'
. self::PATTERN_COMMENT . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. '(' . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_IGNORE
. self::PATTERN_NAMED . self::PATTERN_NAMED
. ')'
. '/s'; . '/s';
/** @var string replace regex for question mark (?) entries */ /** @var string replace regex for question mark (?) entries */
public const REGEX_REPLACE_QUESTION_MARK = '/' public const REGEX_REPLACE_QUESTION_MARK = '/'
. '(' . self::PATTERN_ELEMENT . ')' . self::PATTERN_COMMENT . '|'
. self::PATTERN_COMMENT . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. '(' . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_IGNORE
. self::PATTERN_QUESTION_MARK . self::PATTERN_QUESTION_MARK
. ')'
. '/s'; . '/s';
/** @var string replace regex for numbered ($n) entries */ /** @var string replace regex for numbered ($n) entries */
public const REGEX_REPLACE_NUMBERED = '/' public const REGEX_REPLACE_NUMBERED = '/'
. '(' . self::PATTERN_ELEMENT . ')' . self::PATTERN_COMMENT . '|'
. self::PATTERN_COMMENT . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. '(' . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_IGNORE
. self::PATTERN_NUMBERED . self::PATTERN_NUMBERED
. ')'
. '/s'; . '/s';
/** @var string the main lookup query for all placeholders */ /** @var string the main lookup query for all placeholders */
public const REGEX_LOOKUP_PLACEHOLDERS = '/' public const REGEX_LOOKUP_PLACEHOLDERS = '/'
// prefix string part, must match towards . self::PATTERN_COMMENT . '|'
// seperator for ( = , ? - [and json/jsonb in pg doc section 9.15] . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_ELEMENT . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_COMMENT
// match for replace part // match for replace part
. '(?:' . '(?:'
// ignore parts
. self::PATTERN_IGNORE
// :name named part (PDO) [1] // :name named part (PDO) [1]
. self::PATTERN_NAMED . '|' . self::PATTERN_NAMED . '|'
// ? question mark part (PDO) [2] // ? question mark part (PDO) [2]
@@ -94,6 +75,26 @@ class ConvertPlaceholder
. ')' . ')'
// single line -> add line break to matches in "." // single line -> add line break to matches in "."
. '/s'; . '/s';
/** @var string lookup for only numbered placeholders */
public const REGEX_LOOKUP_NUMBERED = '/'
. self::PATTERN_COMMENT . '|'
. self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
// match for replace part
. '(?:'
// $n numbered part (\PG php) [1]
. self::PATTERN_NUMBERED
// end match
. ')'
. '/s';
/** @var int position for regex in full placeholder lookup: named */
public const LOOOKUP_NAMED_POS = 2;
/** @var int position for regex in full placeholder lookup: question mark */
public const LOOOKUP_QUESTION_MARK_POS = 3;
/** @var int position for regex in full placeholder lookup: numbered */
public const LOOOKUP_NUMBERED_POS = 4;
/** @var int matches position for replacement and single lookup */
public const MATCHING_POS = 2;
/** /**
* Convert PDO type query with placeholders to \PG style and vica versa * Convert PDO type query with placeholders to \PG style and vica versa
@@ -132,11 +133,12 @@ class ConvertPlaceholder
$found = -1; $found = -1;
} }
/** @var array<string> 1: named */ /** @var array<string> 1: named */
$named_matches = array_filter($matches[1]); $named_matches = array_filter($matches[self::LOOOKUP_NAMED_POS]);
/** @var array<string> 2: open ? */ /** @var array<string> 2: open ? */
$qmark_matches = array_filter($matches[2]); $qmark_matches = array_filter($matches[self::LOOOKUP_QUESTION_MARK_POS]);
/** @var array<string> 3: $n matches */ /** @var array<string> 3: $n matches */
$numbered_matches = array_filter($matches[3]); $numbered_matches = array_filter($matches[self::LOOOKUP_NUMBERED_POS]);
// print "**MATCHES**: <pre>" . print_r($matches, true) . "</pre>";
// count matches // count matches
$count_named = count(array_unique($named_matches)); $count_named = count(array_unique($named_matches));
$count_qmark = count($qmark_matches); $count_qmark = count($qmark_matches);
@@ -235,38 +237,37 @@ class ConvertPlaceholder
$empty_params = $converted_placeholders['original']['empty_params']; $empty_params = $converted_placeholders['original']['empty_params'];
switch ($converted_placeholders['type']) { switch ($converted_placeholders['type']) {
case 'named': case 'named':
// 0: full // 1: replace part :named
// 0: full
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part :named
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
self::REGEX_REPLACE_NAMED, self::REGEX_REPLACE_NAMED,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) { function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
// only count up if $match[3] is not yet in lookup table if (!isset($matches[self::MATCHING_POS])) {
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
209
);
}
$match = $matches[self::MATCHING_POS];
// only count up if $match[1] is not yet in lookup table
if (empty($params_lookup[$match])) {
$pos++; $pos++;
$params_lookup[$matches[3]] = '$' . $pos; $params_lookup[$match] = '$' . $pos;
// skip params setup if param list is empty // skip params setup if param list is empty
if (!$empty_params) { if (!$empty_params) {
$params_new[] = $params[$matches[3]] ?? $params_new[] = $params[$match] ??
throw new \RuntimeException( throw new \RuntimeException(
'Cannot lookup ' . $matches[3] . ' in params list', 'Cannot lookup ' . $match . ' in params list',
210 210
); );
} }
} }
// add the connectors back (1), and the data sets only if no replacement will be done // add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . ( return $params_lookup[$match] ??
empty($matches[3]) ? throw new \RuntimeException(
$matches[2] : 'Cannot lookup ' . $match . ' in params lookup list',
$params_lookup[$matches[3]] ?? 211
throw new \RuntimeException( );
'Cannot lookup ' . $matches[3] . ' in params lookup list',
211
)
);
}, },
$converted_placeholders['original']['query'] $converted_placeholders['original']['query']
); );
@@ -276,61 +277,61 @@ class ConvertPlaceholder
// order and data stays the same // order and data stays the same
$params_new = $params ?? []; $params_new = $params ?? [];
} }
// 0: full // 1: replace part ?
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part ?
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
self::REGEX_REPLACE_QUESTION_MARK, self::REGEX_REPLACE_QUESTION_MARK,
function ($matches) use (&$pos, &$params_lookup) { function ($matches) use (&$pos, &$params_lookup) {
if (!isset($matches[self::MATCHING_POS])) {
throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
229
);
}
$match = $matches[self::MATCHING_POS];
// only count pos up for actual replacements we will do // only count pos up for actual replacements we will do
if (!empty($matches[3])) { if (!empty($match)) {
$pos++; $pos++;
$params_lookup[] = '$' . $pos; $params_lookup[] = '$' . $pos;
} }
// add the connectors back (1), and the data sets only if no replacement will be done // add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . ( return '$' . $pos;
empty($matches[3]) ?
$matches[2] :
'$' . $pos
);
}, },
$converted_placeholders['original']['query'] $converted_placeholders['original']['query']
); );
break; break;
case 'numbered': case 'numbered':
// 0: full // 1: replace part $numbered
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part $numbered
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
self::REGEX_REPLACE_NUMBERED, self::REGEX_REPLACE_NUMBERED,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) { function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
// only count up if $match[3] is not yet in lookup table if (!isset($matches[self::MATCHING_POS])) {
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
239
);
}
$match = $matches[self::MATCHING_POS];
// only count up if $match[1] is not yet in lookup table
if (empty($params_lookup[$match])) {
$pos++; $pos++;
$params_lookup[$matches[3]] = ':' . $pos . '_named'; $params_lookup[$match] = ':' . $pos . '_named';
// skip params setup if param list is empty // skip params setup if param list is empty
if (!$empty_params) { if (!$empty_params) {
$params_new[] = $params[($pos - 1)] ?? $params_new[] = $params[($pos - 1)] ??
throw new \RuntimeException( throw new \RuntimeException(
'Cannot lookup ' . ($pos - 1) . ' in params list', 'Cannot lookup ' . ($pos - 1) . ' in params list',
220 230
); );
} }
} }
// add the connectors back (1), and the data sets only if no replacement will be done // add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . ( return $params_lookup[$match] ??
empty($matches[3]) ? throw new \RuntimeException(
$matches[2] : 'Cannot lookup ' . $match . ' in params lookup list',
$params_lookup[$matches[3]] ?? 231
throw new \RuntimeException( );
'Cannot lookup ' . $matches[3] . ' in params lookup list',
221
)
);
}, },
$converted_placeholders['original']['query'] $converted_placeholders['original']['query']
); );

View File

@@ -1371,7 +1371,7 @@ class Generate
) { ) {
$this->msg .= sprintf( $this->msg .= sprintf(
$this->l->__('Please enter a valid (%s) input for the <b>%s</b> Field!<br>'), $this->l->__('Please enter a valid (%s) input for the <b>%s</b> Field!<br>'),
$this->dba->getTableArray()[$key]['error_example'], $this->dba->getTableArray()[$key]['error_example'] ?? '[MISSING]',
$this->dba->getTableArray()[$key]['output_name'] $this->dba->getTableArray()[$key]['output_name']
); );
} }
@@ -2602,7 +2602,7 @@ class Generate
} }
} }
// add lost error ones // add lost error ones
$this->log->error('P: ' . $data['prefix'] . ', ' $this->log->error('Prefix: ' . $data['prefix'] . ', '
. Support::prAr($_POST['ERROR'][$data['prefix']] ?? [])); . Support::prAr($_POST['ERROR'][$data['prefix']] ?? []));
if ($this->error && !empty($_POST['ERROR'][$data['prefix']])) { if ($this->error && !empty($_POST['ERROR'][$data['prefix']])) {
$prfx = $data['prefix']; // short $prfx = $data['prefix']; // short

View File

@@ -182,6 +182,7 @@ class EditUsers implements Interface\TableArraysInterface
'type' => 'text', 'type' => 'text',
'error_check' => 'unique|custom', 'error_check' => 'unique|custom',
'error_regex' => "/^[A-Za-z0-9]+$/", 'error_regex' => "/^[A-Za-z0-9]+$/",
'error_example' => "ABCdef123",
'emptynull' => 1,'min_edit_acl' => '100', 'emptynull' => 1,'min_edit_acl' => '100',
'min_show_acl' => '100', 'min_show_acl' => '100',
], ],