Commit ee6ad621 authored by Carsten  Rose's avatar Carsten Rose
Browse files

Evalulate.php: user can optional define that single or double ticks in replaced token are escaped.

parent ee5044e8
...@@ -313,6 +313,10 @@ const SIP_URLPARAM = 'urlparam'; ...@@ -313,6 +313,10 @@ const SIP_URLPARAM = 'urlparam';
const VAR_RANDOM = 'random'; const VAR_RANDOM = 'random';
// TOKEN evaluate
const TOKEN_ESCAPE_SINGLE_TICK = 's';
const TOKEN_ESCAPE_DOUBLE_TICK = 'd';
const TOKEN_FOUND_IN_STORE_QUERY = 'query';
const RANDOM_LENGTH = 32; const RANDOM_LENGTH = 32;
...@@ -434,6 +438,7 @@ const FE_PATH_FILE_NAME = 'pathFileName'; // Target pathFilename for an uploaded ...@@ -434,6 +438,7 @@ const FE_PATH_FILE_NAME = 'pathFileName'; // Target pathFilename for an uploaded
const FE_SQL_VALIDATE = 'sqlValidate'; // Action: Query to validate form load const FE_SQL_VALIDATE = 'sqlValidate'; // Action: Query to validate form load
const FE_EXPECT_RECORDS = 'expectRecords'; // Action: expected number of rows of FE_SQL_VALIDATE const FE_EXPECT_RECORDS = 'expectRecords'; // Action: expected number of rows of FE_SQL_VALIDATE
const FE_MESSAGE_FAIL = 'messageFail'; // Action: Message to display, if FE_SQL_VALIDATE fails. const FE_MESSAGE_FAIL = 'messageFail'; // Action: Message to display, if FE_SQL_VALIDATE fails.
const FE_REQUIRED_LIST = 'requiredList'; // Optional list of FormElements which have to be non empty to make this 'action'-FormElement active.
const FE_SLAVE_ID = 'slaveId'; // Action; Value or Query to compute id of slave record. const FE_SLAVE_ID = 'slaveId'; // Action; Value or Query to compute id of slave record.
const FE_SQL_UPDATE = 'sqlUpdate'; // Action: Update Statement for slave record const FE_SQL_UPDATE = 'sqlUpdate'; // Action: Update Statement for slave record
const FE_SQL_INSERT = 'sqlInsert'; // Action: Insert Statement to create slave record. const FE_SQL_INSERT = 'sqlInsert'; // Action: Insert Statement to create slave record.
......
...@@ -147,15 +147,16 @@ class Evaluate { ...@@ -147,15 +147,16 @@ class Evaluate {
/** /**
* Tries to substitute $token. * Tries to substitute $token.
* Token might be * Token might be:
* a) a SQL statement to fire * a) a SQL statement to fire
* b) fetch from a store. Syntax: 'form', 'form:C', 'form:SC0', 'form:S:ALNUMX' * b) fetch from a store. Syntax: 'form', 'form:C', 'form:SC0', 'form:S:alnumx', 'form:F:all:s'
*
* The token have to be _without_ Delimiter '{{' / '}}' * The token have to be _without_ Delimiter '{{' / '}}'
* If neither a) or b) match, return the token itself, surrounded by single ticks, to emphase that substition failed. * If neither a) or b) match, return the token itself.
* *
* @param $token * @param $token
* @param string $foundInStore Returns the name of the store where $key has been found. If $key is not found, return ''. * @param string $foundInStore Returns the name of the store where $key has been found. If $key is not found, return ''.
* @return array|mixed|null|string * @return array|null|string
* @throws CodeException * @throws CodeException
* @throws DbException * @throws DbException
*/ */
...@@ -175,21 +176,31 @@ class Evaluate { ...@@ -175,21 +176,31 @@ class Evaluate {
// SQL Statement? // SQL Statement?
if (in_array(strtoupper($arr[0] . ' '), $this->sqlKeywords)) { if (in_array(strtoupper($arr[0] . ' '), $this->sqlKeywords)) {
$foundInStore = 'query'; $foundInStore = TOKEN_FOUND_IN_STORE_QUERY;
return $this->db->sql($token, $sqlMode); return $this->db->sql($token, $sqlMode);
} }
// explode for: <key>:<store priority>:<sanitize class> // explode for: <key>:<store priority>:<sanitize class>:<escape>
$arr = explode(':', $token, 3); $arr = explode(':', $token, 4);
if (!isset($arr[1])) $arr = array_merge($arr, [null, null, null, null]); // fake isset()
$arr[1] = null;
if (!isset($arr[2]))
$arr[2] = null;
// search for value in stores // search for value in stores
$value = $this->store->getVar($arr[0], $arr[1], $arr[2], $foundInStore); $value = $this->store->getVar($arr[0], $arr[1], $arr[2], $foundInStore);
// escape ticks
if (is_string($value)) {
switch ($arr[3]) {
case TOKEN_ESCAPE_SINGLE_TICK:
$value = str_replace("'", "\\'", $value);
break;
case TOKEN_ESCAPE_DOUBLE_TICK:
$value = str_replace('"', '\\"', $value);
break;
default:
break;
}
}
// OLD: nothing replaced: put ticks around, to sanitize strings for SQL statements. Nothing to substitute is not a wished situation. // OLD: nothing replaced: put ticks around, to sanitize strings for SQL statements. Nothing to substitute is not a wished situation.
// return ($value === false) ? "'" . $token . "'" : $value; // return ($value === false) ? "'" . $token . "'" : $value;
......
...@@ -100,7 +100,6 @@ class EvaluateTest extends \AbstractDatabaseTest { ...@@ -100,7 +100,6 @@ class EvaluateTest extends \AbstractDatabaseTest {
$this->store->setVar('a', '{{b:C:all}}', 'C'); $this->store->setVar('a', '{{b:C:all}}', 'C');
$this->store->setVar('b', 'Holiday', 'C'); $this->store->setVar('b', 'Holiday', 'C');
$this->assertEquals('Holiday', $eval->parse('{{SELECT name FROM Person WHERE name=\'{{a:C:all}}\'}}')); $this->assertEquals('Holiday', $eval->parse('{{SELECT name FROM Person WHERE name=\'{{a:C:all}}\'}}'));
} }
public function testParseArray() { public function testParseArray() {
...@@ -116,8 +115,6 @@ class EvaluateTest extends \AbstractDatabaseTest { ...@@ -116,8 +115,6 @@ class EvaluateTest extends \AbstractDatabaseTest {
]; ];
$this->assertEquals($expected, $eval->parseArray($data)); $this->assertEquals($expected, $eval->parseArray($data));
} }
public function testParseArrayOfArray() { public function testParseArrayOfArray() {
...@@ -146,6 +143,173 @@ class EvaluateTest extends \AbstractDatabaseTest { ...@@ -146,6 +143,173 @@ class EvaluateTest extends \AbstractDatabaseTest {
$this->assertEquals($expected, $eval->parseArray($data)); $this->assertEquals($expected, $eval->parseArray($data));
} }
public function testSubstituteSql() {
$eval = new \qfq\Evaluate($this->store, $this->db);
$expectArr = [0 => ['name' => 'Holiday', 'firstname' => 'Billie'], 1 => ['name' => 'Holiday', 'firstname' => 'Billie']];
$eval->parse('{{INSERT INTO Person (name, firstname) VALUES (\'Holiday\', \'Billie\')}}');
$eval->parse('{{INSERT INTO Person (name, firstname) VALUES (\'Holiday\', \'Billie\')}}');
$this->store->setVar('a', '{{b:C:all}}', 'C');
$this->store->setVar('b', 'Holiday', 'C');
// Fire query: empty
$this->assertEquals('', $eval->substitute('SELECT name FROM Person WHERE id=0', $foundInStore));
$this->assertEquals(TOKEN_FOUND_IN_STORE_QUERY, $foundInStore);
// Fire query
$this->assertEquals('Holiday', $eval->substitute('SELECT name FROM Person WHERE name LIKE "Holiday" LIMIT 1', $foundInStore));
$this->assertEquals(TOKEN_FOUND_IN_STORE_QUERY, $foundInStore);
// Fire query, surrounding whitespace
$this->assertEquals('Holiday', $eval->substitute(' SELECT name FROM Person WHERE name LIKE "Holiday" LIMIT 1 ', $foundInStore));
$this->assertEquals(TOKEN_FOUND_IN_STORE_QUERY, $foundInStore);
// Fire query, lowercase
$this->assertEquals('Holiday', $eval->substitute(' SELECT name FROM Person WHERE name LIKE "Holiday" LIMIT 1', $foundInStore));
$this->assertEquals(TOKEN_FOUND_IN_STORE_QUERY, $foundInStore);
// Fire query, join two records
$this->assertEquals('HolidayBillieHolidayBillie', $eval->substitute('SELECT name, firstname FROM Person WHERE name LIKE "Holiday" LIMIT 2', $foundInStore));
$this->assertEquals(TOKEN_FOUND_IN_STORE_QUERY, $foundInStore);
// Fire query, two records as assoc
$this->assertEquals($expectArr, $eval->substitute('!SELECT name, firstname FROM Person WHERE name LIKE "Holiday" LIMIT 2', $foundInStore));
$this->assertEquals(TOKEN_FOUND_IN_STORE_QUERY, $foundInStore);
// Fire query, no result
$this->assertEquals(array(), $eval->substitute('!SELECT name, firstname FROM Person WHERE id=0', $foundInStore));
$this->assertEquals(TOKEN_FOUND_IN_STORE_QUERY, $foundInStore);
// Fire query, two records as assoc, surrounding whitespace
$this->assertEquals($expectArr, $eval->substitute(' !select name, firstname FROM Person WHERE name LIKE "Holiday" LIMIT 2 ', $foundInStore));
$this->assertEquals(TOKEN_FOUND_IN_STORE_QUERY, $foundInStore);
}
public function testSubstituteVar() {
$eval = new \qfq\Evaluate($this->store, $this->db);
// Retrieve in PRIO `FSRD`
$this->store->setVar('a', '1234', STORE_TABLE_DEFAULT, true);
$this->assertEquals('1234', $eval->substitute('a', $foundInStore));
$this->assertEquals(STORE_TABLE_DEFAULT, $foundInStore);
$this->store->setVar('a', '12345', STORE_RECORD, true);
$this->assertEquals('12345', $eval->substitute('a', $foundInStore));
$this->assertEquals(STORE_RECORD, $foundInStore);
$this->store->setVar('a', '123456', STORE_SIP, true);
$this->assertEquals('123456', $eval->substitute('a', $foundInStore));
$this->assertEquals(STORE_SIP, $foundInStore);
$this->store->setVar('a', '1234567', STORE_FORM, true);
$this->assertEquals('1234567', $eval->substitute('a', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
$this->assertEquals(false, $eval->substitute('notFound', $foundInStore));
$this->assertEquals('', $foundInStore);
// Specific Store
$this->assertEquals('1234', $eval->substitute('a:D', $foundInStore));
$this->assertEquals(STORE_TABLE_DEFAULT, $foundInStore);
$this->assertEquals('12345', $eval->substitute('a:R', $foundInStore));
$this->assertEquals(STORE_RECORD, $foundInStore);
$this->assertEquals('123456', $eval->substitute('a:S', $foundInStore));
$this->assertEquals(STORE_SIP, $foundInStore);
$this->assertEquals('1234567', $eval->substitute('a:F', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
$this->assertEquals(false, $eval->substitute('a:V', $foundInStore));
$this->assertEquals('', $foundInStore);
// Sanatize Class: digits
$this->assertEquals('1234567', $eval->substitute('a:F:digit', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
$this->assertEquals('1234567', $eval->substitute('a:F:alnumx', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
$this->assertEquals('1234567', $eval->substitute('a:F:allbut', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
$this->assertEquals('1234567', $eval->substitute('a:F:all', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
// Sanatize Class: text
$this->store->setVar('a', 'Hello world @-_.,;: /()', STORE_FORM, true);
$this->assertEquals('', $eval->substitute('a:F:digit', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
$this->assertEquals('Hello world @-_.,;: /()', $eval->substitute('a:F:alnumx', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
$this->assertEquals('Hello world @-_.,;: /()', $eval->substitute('a:F:allbut', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
$this->assertEquals('Hello world @-_.,;: /()', $eval->substitute('a:F:all', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
}
public function testSubstituteVarEscape() {
$eval = new \qfq\Evaluate($this->store, $this->db);
// No escape
$this->store->setVar('a', 'hello', STORE_FORM, true);
$this->assertEquals('hello', $eval->substitute('a:F:all', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
// None, Single Tick
$this->store->setVar('a', 'hello', STORE_FORM, true);
$this->assertEquals('hello', $eval->substitute('a:F:all:s', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
// None, Double Tick
$this->store->setVar('a', 'hello', STORE_FORM, true);
$this->assertEquals('hello', $eval->substitute('a:F:all:d', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
// ', Single Tick
$this->store->setVar('a', 'hel\'lo', STORE_FORM, true);
$this->assertEquals('hel\\\'lo', $eval->substitute('a:F:all:s', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
// ' Double Tick
$this->store->setVar('a', 'hel\'lo', STORE_FORM, true);
$this->assertEquals('hel\'lo', $eval->substitute('a:F:all:d', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
// ", Single Tick
$this->store->setVar('a', 'hel"lo', STORE_FORM, true);
$this->assertEquals('hel"lo', $eval->substitute('a:F:all:s', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
// ", Double Tick
$this->store->setVar('a', 'hel"lo', STORE_FORM, true);
$this->assertEquals('hel\"lo', $eval->substitute('a:F:all:d', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
// Multi ', Single Tick
$this->store->setVar('a', "h\"e' 'l\"lo ' ", STORE_FORM, true);
$this->assertEquals("h\"e\' \'l\"lo \' ", $eval->substitute('a:F:all:s', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
// Multi , Double Tick
$this->store->setVar('a', 'h"e\' \'l"lo \' ', STORE_FORM, true);
$this->assertEquals('h\"e\' \'l\"lo \' ', $eval->substitute('a:F:all:d', $foundInStore));
$this->assertEquals(STORE_FORM, $foundInStore);
}
protected function setUp() { protected function setUp() {
$this->store = Store::getInstance('form=TestFormName', true); $this->store = Store::getInstance('form=TestFormName', true);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment