Commit 84b1011d authored by Carsten  Rose's avatar Carsten Rose
Browse files

Merge branch 'F12636InputSingleTickEncode' into 'develop'

Refs #12636. Form: a) Single Tick encode for {{10.line.content::s}}, b) new...

See merge request !352
parents 045802ef 1de3c188
Pipeline #5326 passed with stages
in 3 minutes and 15 seconds
......@@ -1546,7 +1546,19 @@ Type: editor
^^^^^^^^^^^^
* TinyMCE (https://www.tinymce.com, community edition) is used as the QFQ Rich Text Editor.
* The content will be saved as HTML inside the database.
* The content will be saved as HTML code in the database.
.. important::
*FormElement.encode*: To save HTML code, incl. HTML tags (bold, table, lists, ...), the **htmspecialchar**
encoding can't be used, cause the HTML tags loose their meaning. Therefore **single tick** or **none** is necessary.
* *FormElement.checktype*
* *all*: The only useful setting for Editor. HTML tags might contain ``% ' " < >`` and so on. This is **dangerous**
due of potential inserted malicous code! But there is no other option, cause the HTML tags are required.
* All configuration and plugins will be configured via the 'parameter' field. Just prepend the word 'editor-' in front
of each TinyMCE keyword. Check possible options under:
......
......@@ -1899,7 +1899,7 @@ Output::
31.12.2019 23:55 / - / -
.. _qlugify:
.. _qslugify:
QSLUGIFY: clean a string
^^^^^^^^^^^^^^^^^^^^^^^^
......@@ -1909,12 +1909,77 @@ Non alphanumerical characters are stripped off. Spaces are replaced by '-'. All
Example::
10.sql = SELECT QSLUGIFY('abcd ABCD ae.ä.oe.ö.ue.ü z[]{}()<>.,?Z')
10.sql = SELECT QSLUGIFY('abcd ABCD ae.ä.oe.ö.ue.ü z[]{}()<>.,?Z')
Output::
abcd-abcd-ae-a-oe-o-ue-u-z-z
.. _qent_squote:
QENT_SQUOTE: convert single tick to HTML entity &apos;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Convert all single ticks in a string to the HTML entity "&apos;"
Example::
10.sql = SELECT QENT_SQUOTE("John's car")
Output::
John&apos;s car
.. _qent_dquote:
QENT_DQUOTE: convert double tick to HTML entity &quot;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Convert all double ticks in a string to the HTML entity "&quot;"
Example::
10.sql = SELECT QENT_SQUOTE('A "nice" event')
Output::
A &quot;nice&quot; event
.. _qesc_squote:
QESC_SQUOTE: escape single tick
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Escape all single ticks with a backslash. Double escaped single ticks (two backslashes) will be replaced by a single
escaped single tick.
Example::
Be Music.style = "Rock'n' Roll"
10.sql = SELECT QESC_SQUOTE(style) FROM Music
Output::
Rock\'n\'n Roll
.. _qesc_dquote:
QESC_SQUOTE: escape double tick
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Escape all double ticks with a backslash. Double escaped double ticks (two backslashes) will replaced by a single
escaped double tick.
Example::
Set Comment.note = 'A "nice" event'
10.sql = SELECT QESC_DQUOTE(style) FROM Music
Output::
Rock\'n\'n Roll
.. _strip_tags:
strip_tags: strip html tags
......@@ -1962,11 +2027,12 @@ Example tt-content record for the function::
render = api
100 {
  sql = SELECT p.firstName AS _firstName
               , NOW() AS now, CONCAT('p:{{pageAlias:T}}&form=person&r=', p.id ) AS '_pagee|_hide|myLink'            
          FROM Person AS p
          WHERE p.id={{pId:R}}
}                       
sql = SELECT p.firstName AS _firstName
, NOW() AS now
, CONCAT('p:{{pageAlias:T}}&form=person&r=', p.id ) AS '_pagee|_hide|myLink'
FROM Person AS p
WHERE p.id={{pId:R}}
}
Example tt-content record for the calling report::
......@@ -1976,24 +2042,24 @@ Example tt-content record for the calling report::
#
10 {
  sql = SELECT p.id AS _pId, p.name FROM Person AS p ORDER BY p.name
  head = <table class="table"><tr><th>Name</th><th>Firstname</th><th>Link (final)</th><th>Link (source)</th><th>NOW() (via Output)</th></tr>
  tail = </table>
  rbeg = <tr>
  renr = </tr>
  fbeg = <td>
  fend = </td>
                                                                    
  20 {
    function = getFirstName(pId) => firstName, myLink    
  }
                                                                    
  30 {
    sql = SELECT '{{firstName:R}}', "{{myLink:R}}", "{{&myLink:R}}", '{{_output:R}}'
    fbeg = <td>
    fend = </td>
  }
}                                                                    
sql = SELECT p.id AS _pId, p.name FROM Person AS p ORDER BY p.name
head = <table class="table"><tr><th>Name</th><th>Firstname</th><th>Link (final)</th><th>Link (source)</th><th>NOW() (via Output)</th></tr>
tail = </table>
rbeg = <tr>
renr = </tr>
fbeg = <td>
fend = </td>
20 {
function = getFirstName(pId) => firstName, myLink
}
30 {
sql = SELECT '{{firstName:R}}', "{{myLink:R}}", "{{&myLink:R}}", '{{_output:R}}'
fbeg = <td>
fend = </td>
}
}
Explanation:
......
......@@ -21,6 +21,7 @@ use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Helper\Sanitize;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Helper\OnString;
use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Report\Report;
use IMATHUZH\Qfq\Core\Store\Sip;
......@@ -862,6 +863,8 @@ abstract class AbstractBuildForm {
if ($formElement[FE_ENCODE] === FE_ENCODE_SPECIALCHAR) {
// $value = htmlspecialchars_decode($value, ENT_QUOTES);
$value = Support::htmlEntityEncodeDecode(MODE_DECODE, $value);
} elseif ($formElement[FE_ENCODE] === FE_ENCODE_SINGLE_TICK) {
$value = OnString::escapeSingleTickInHtml($value);
}
// Typically: $htmlElementNameIdZero = true
......
......@@ -1332,6 +1332,7 @@ const TYPEAHEAD_PLACEHOLDER = '?';
// Values
const FE_ENCODE_SPECIALCHAR = 'specialchar';
const FE_ENCODE_SINGLE_TICK = 'single tick';
const FE_ENCODE_NONE = 'none';
const FE_FILE_CAPTURE_CAMERA = 'camera';
......
......@@ -204,6 +204,11 @@ $UPDATE_ARRAY = array(
"ALTER TABLE `FormSubmitLog` ADD `formName` VARCHAR(255) NOT NULL DEFAULT '' AFTER `formId`;",
],
'21.6.0' => [
"ALTER TABLE `FormElement` CHANGE `encode` `encode` ENUM('none','specialchar','single tick') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'specialchar';",
],
);
......
......@@ -249,7 +249,7 @@ class Evaluate {
* @throws \UserFormException
* @throws \UserReportException
*/
private function inlineLink($arrToken, $dbIndex, &$foundInStore) {
private function inlineLink($arrToken, $dbIndex, &$foundInStore): string {
$token = OnString::trimQuote(trim(implode(' ', $arrToken)));
......@@ -262,31 +262,6 @@ class Evaluate {
return $this->link->renderLink($token);
}
/**
* Get the CET/CEST Timezone for a given date, or if date is '' based on the current date.
*
* @param string $dateStr
* @return string // CET, CEST or GMT+?/GMT-?
*/
public function getEuropeanTimezone($dateStr = '') {
$ts = ($dateStr == '') ? time() : strtotime($dateStr);
$offset = date("Z", $ts) / 3600;
switch ($offset) {
case 1:
$tz = "CET";
break;
case 2:
$tz = "CEST";
break;
default:
$tz = 'GMT' . sprintf("%+d", $offset);
}
return $tz;
}
/**
* @param $arrToken
* @param $dbIndex
......@@ -296,7 +271,7 @@ class Evaluate {
* @throws \UserFormException
* @throws \UserReportException
*/
private function inlineDataDndApi($arrToken, $dbIndex, &$foundInStore) {
private function inlineDataDndApi($arrToken, $dbIndex, &$foundInStore): string {
$token = OnString::trimQuote(trim(implode(' ', $arrToken)));
......@@ -344,7 +319,7 @@ class Evaluate {
$token = trim($token);
$dbIndex = $this->dbIndex;
$flagWipe = false;
$rcFlagWipe = false;
// Check if the $token starts with '[<int>]...' - yes: open the necessary database.
if (strlen($token) > 2 && $token[0] === '[') {
......@@ -421,64 +396,7 @@ class Evaluate {
$value = $this->store::getVar($arrToken[VAR_INDEX_VALUE], $arrToken[VAR_INDEX_STORE], $arrToken[VAR_INDEX_SANITIZE],
$foundInStore, $typeMessageViolate, $arrToken[VAR_INDEX_DEFAULT]);
// escape ticks
if (is_string($value)) {
// Process all escape requests in the given order.
for ($ii = 0; $ii < strlen($escapeTypes); $ii++) {
$escape = $escapeTypes[$ii];
if ($escape == TOKEN_ESCAPE_CONFIG) {
$escape = $this->escapeTypeDefault;
}
switch ($escape) {
case TOKEN_ESCAPE_SINGLE_TICK:
$value = str_replace("'", "\\'", $value);
break;
case TOKEN_ESCAPE_DOUBLE_TICK:
$value = str_replace('"', '\\"', $value);
break;
case TOKEN_ESCAPE_COLON:
$value = str_replace(':', '\\:', $value);
break;
case TOKEN_ESCAPE_LDAP_FILTER:
$value = Support::ldap_escape($value, null, LDAP_ESCAPE_FILTER);
break;
case TOKEN_ESCAPE_LDAP_DN:
$value = Support::ldap_escape($value, null, LDAP_ESCAPE_DN);
break;
case TOKEN_ESCAPE_MYSQL:
$value = $this->dbArray[$dbIndex]->realEscapeString($value);
break;
case TOKEN_ESCAPE_NONE: // do nothing
break;
case TOKEN_ESCAPE_PASSWORD_T3FE:
$value = T3Handler::getHash($value);
break;
case TOKEN_ESCAPE_STOP_REPLACE:
$value = Support::encryptDoubleCurlyBraces($value);
break;
case TOKEN_ESCAPE_EXCEPTION:
// empty values will be handled later.
break;
case TOKEN_ESCAPE_WIPE:
$flagWipe = true;
break;
case TOKEN_ESCAPE_TIMEZONE:
$value = $this->getEuropeanTimezone($value);
break;
case TOKEN_ESCAPE_HTML_SPECIAL_CHAR:
$value = Support::htmlEntityEncodeDecode(MODE_ENCODE, $value);
break;
default:
throw new \UserFormException("Unknown escape qualifier: $escape", ERROR_UNKNOW_SANITIZE_CLASS);
break;
}
}
} else {
// In case the value is not found and the escape class forces a full stop
if (strpos($escapeTypes, TOKEN_ESCAPE_EXCEPTION) !== false) {
throw new \UserFormException($arrToken[VAR_INDEX_MESSAGE] ?? '', ERROR_QUIT_QFQ_REGULAR);
}
}
$value = OnString::escape($escapeTypes, $value, $rcFlagWipe);
// Not found and a default is given: take the default.
if ($foundInStore == '' && $arrToken[VAR_INDEX_DEFAULT] != '') {
......@@ -486,7 +404,7 @@ class Evaluate {
$value = str_replace('\\:', ':', $arrToken[VAR_INDEX_DEFAULT]);
}
if ($flagWipe) {
if ($rcFlagWipe) {
switch ($foundInStore) {
case STORE_SIP:
$this->store::unsetVar($arrToken[VAR_INDEX_VALUE], STORE_SIP);
......
......@@ -9,12 +9,26 @@
namespace IMATHUZH\Qfq\Core\Helper;
use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Typo3\T3Handler;
/**
* Class OnString
* @package qfq
*/
class OnString {
/**
* @var Store
*/
private static $store = null;
/**
* @var Database
*/
private static $db = null;
/**
* Returns part of haystack string starting from and including the last occurrence of needle to the end of haystack.
*
......@@ -372,5 +386,264 @@ class OnString {
$rcFunctionParam = OnArray::trimArray(explode(',', $args[0] ?? ''));
$rcReturnParam = OnArray::trimArray(explode(',', $split[1] ?? ''));
}
/**
* Escape $value by list of $escapeTypes.
*
* @param $escapeTypes
* @param $value
* @param $rcFlagWipe
* @return string
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
public static function escape($escapeTypes, $value, &$rcFlagWipe) {
// escape ticks
if (is_string($value)) {
// Process all escape requests in the given order.
for ($ii = 0; $ii < strlen($escapeTypes); $ii++) {
$escape = $escapeTypes[$ii];
if ($escape == TOKEN_ESCAPE_CONFIG) {
$escape = self::getEscapeTypeDefault();
}
switch ($escape) {
case TOKEN_ESCAPE_SINGLE_TICK:
$value = str_replace("'", "\\'", $value);
break;
case TOKEN_ESCAPE_DOUBLE_TICK:
$value = str_replace('"', '\\"', $value);
break;
case TOKEN_ESCAPE_COLON:
$value = str_replace(':', '\\:', $value);
break;
case TOKEN_ESCAPE_LDAP_FILTER:
$value = Support::ldap_escape($value, null, LDAP_ESCAPE_FILTER);
break;
case TOKEN_ESCAPE_LDAP_DN:
$value = Support::ldap_escape($value, null, LDAP_ESCAPE_DN);
break;
case TOKEN_ESCAPE_MYSQL:
if (self::$db === null) {
self::$db = new Database();
}
$value = self::$db->realEscapeString($value);
break;
case TOKEN_ESCAPE_NONE: // do nothing
break;
case TOKEN_ESCAPE_PASSWORD_T3FE:
$value = T3Handler::getHash($value);
break;
case TOKEN_ESCAPE_STOP_REPLACE:
$value = Support::encryptDoubleCurlyBraces($value);
break;
case TOKEN_ESCAPE_EXCEPTION:
// empty values will be handled in 'else'.
break;
case TOKEN_ESCAPE_WIPE:
$rcFlagWipe = true;
break;
case TOKEN_ESCAPE_TIMEZONE:
$value = self::getEuropeanTimezone($value);
break;
case TOKEN_ESCAPE_HTML_SPECIAL_CHAR:
$value = Support::htmlEntityEncodeDecode(MODE_ENCODE, $value);
break;
default:
throw new \UserFormException("Unknown escape qualifier: $escape", ERROR_UNKNOW_SANITIZE_CLASS);
break;
}
}
} else {
// In case the value is not found and the escape class forces a full stop
if (strpos($escapeTypes, TOKEN_ESCAPE_EXCEPTION) !== false) {
throw new \UserFormException($arrToken[VAR_INDEX_MESSAGE] ?? '', ERROR_QUIT_QFQ_REGULAR);
}
}
return $value;
}
/**
* @return string
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
private static function getEscapeTypeDefault() {
static $escapeTypeDefault = null;
if ($escapeTypeDefault === null) {
self::$store = Store::getInstance();
$escapeTypeDefault = self::$store->getVar(F_ESCAPE_TYPE_DEFAULT, STORE_SYSTEM);
}
return $escapeTypeDefault;
}
/**
* Get the CET/CEST Timezone for a given date, or if date is '' based on the current date.
*
* @param string $dateStr
* @return string
*/
public static function getEuropeanTimezone($dateStr = '') {
$ts = ($dateStr == '') ? time() : strtotime($dateStr);
$offset = date("Z", $ts) / 3600;
switch ($offset) {
case 1:
$tz = "CET";
break;
case 2:
$tz = "CEST";
break;
default:
$tz = 'GMT' . sprintf("%+d", $offset);
}
return $tz;
}
/**
* Replaces single tick in a HTML code by html entity
* or if a attribute is quoted with single tick convert the attribute to double quoted.
* If there is a double quote in single tick quoted attribute, it's replaced by '&quot;'.
*
* Content (outside of an HTML tag): single ticks will be replaced by '&apos;':
* <b>John's</b> >> <b>John&apos;s</b>
*
* HTML tag attribute: A single tick quoted attribute will be converted to a double tick quoted attribute.
* <img title='Moon'> >> <img title="Moon">
*
* HTML tag attribute: A single ticks in a double tick quoted string will be replaced by '&apos;'
* <img title='echo "hello"'> >> <img title="echo &quot;hello&quot;'>
*
* @param $line
* @return string
*/
public static function escapeSingleTickInHtml($line): string {
$flagTag = false;
$flagAttribute = false;
$flagAttributeStartSingleTick = false;
$flagAttributeStartDoubleTick = false;
$flagAttributeStarted = false;
$posStartSingleTick = null;
$new = '';
$length = strlen($line);
for ($ii = 0; $ii < $length; $ii++) {
$c = $line[$ii];
switch ($c) {
case '>':
// HTML tag ends here: close all open flags
$flagTag = false;
$flagAttribute = false;
$flagAttributeStartSingleTick = false;
$flagAttributeStartDoubleTick = false;
$flagAttributeStarted = false;
$posStartSingleTick = null;
break;
case '<':
// HTML tag starts here
$flagTag = true;
break;
case '=':
if ($flagTag && !$flagAttribute) {
// Inside a tag and outside a running attribute: here starts a new attribute
$flagAttribute = true;
}
break;
case ' ':
// Space after start attribute and no quotes used, means: attribute ends here.
if ($flagTag && $flagAttribute && $flagAttributeStarted && !$flagAttributeStartSingleTick && !$flagAttributeStartDoubleTick) {
$flagAttribute = false;
$flagAttributeStartSingleTick = false;
$flagAttributeStartDoubleTick = false;
$flagAttributeStarted = false;
$posStartSingleTick = null;
}
break;
case "'":
if ($flagTag) {
if ($flagAttribute) {
if (!$flagAttributeStartSingleTick && !$flagAttributeStartDoubleTick && !$flagAttributeStarted) {
// Attribute quoted by single tick.
// Remember position to later replace by double tick, if there is no double tick inside.
$flagAttributeStartSingleTick = true;
$posStartSingleTick = strlen($new);
$flagAttributeStarted = true;
break;
}
if ($flagAttributeStartSingleTick) {
// Closing single tick. Attribute ends here.
$flagAttributeStartSingleTick = false;
$flagAttribute = false;
$flagAttributeStarted = false;
// No double tick found: single ticks can be replaced by double tick
$new[$posStartSingleTick] = '"';
$new .= '"';
continue 2;
}
if ($flagAttributeStartDoubleTick) {
// Single tick inside a double tick quoted attribute: can be replaced
$new .= '&apos;';
continue 2;
}
}
} else {
// regular content (no tag)
$new .= '&apos;';
continue 2;
}
break;
case '"':
if ($flagTag) {
if ($flagAttribute) {
if (!$flagAttributeStartSingleTick && !$flagAttributeStartDoubleTick && !$flagAttributeStarted) {
// Attribute starting with double tick.
$flagAttributeStartDoubleTick = true;
$flagAttributeStarted = true;
break;
}
if ($flagAttributeStartDoubleTick) {
// Attribute ending with double tick
$flagAttributeStartDoubleTick = false;
$flagAttribute = false;
$flagAttributeStarted = false;