diff --git a/api/src/Db.php b/api/src/Db.php index af9c1bad2a..99eedbe234 100644 --- a/api/src/Db.php +++ b/api/src/Db.php @@ -757,7 +757,7 @@ class Db * @throws Db\Exception\InvalidSql for SQL syntax errors * @throws Db\Exception with $this->Link_ID->ErrorNo() as code for all other errors */ - function query($Query_String, $line = '', $file = '', $offset=0, $num_rows=-1, $inputarr=false, $fetchmode=self::FETCH_BOTH, $reconnect=true) + function query($Query_String, $line = '', $file = '', int $offset=0, int $num_rows=-1, $inputarr=false, $fetchmode=self::FETCH_BOTH, $reconnect=true) { if ($Query_String == '') { @@ -2291,7 +2291,7 @@ class Db { return $sql; } - return $this->query($sql,$line,$file,$offset,$offset===False ? -1 : (int)$num_rows,false,$fetchmode); + return $this->query($sql, $line, $file, (int)$offset, $offset===False ? -1 : (int)$num_rows, false, $fetchmode); } /** diff --git a/api/src/Etemplate/Widget/Nextmatch.php b/api/src/Etemplate/Widget/Nextmatch.php index 1980c5bbc5..fc4eec4494 100644 --- a/api/src/Etemplate/Widget/Nextmatch.php +++ b/api/src/Etemplate/Widget/Nextmatch.php @@ -336,7 +336,7 @@ class Nextmatch extends Etemplate\Widget // Parse sort into something that get_rows functions are expecting: db_field in order, ASC/DESC in sort if(is_array($value['sort'])) { - $value['order'] = $value['sort']['id']; + $value['order'] = preg_match('/^[a-z0-9_]+$/', $value['sort']['id']) ? $value['sort']['id'] : ''; $value['sort'] = $value['sort']['asc'] ? 'ASC' : 'DESC'; } diff --git a/api/src/Link.php b/api/src/Link.php index 3e1c0b738d..1dba0bea35 100644 --- a/api/src/Link.php +++ b/api/src/Link.php @@ -812,6 +812,11 @@ class Link extends Link\Storage $options['num_rows'] = max((int)$GLOBALS['egw_info']['user']['preference']['common']['maxmatchs'], self::DEFAULT_NUM_ROWS); } + if (isset($options['order']) && !preg_match(preg_match('/^[a-z0-9_]+$/', $options['order']))) + { + unset($options['order'], $options['sort']); + } + $result = self::exec($method, array($pattern, &$options)); if (!isset($options['total'])) diff --git a/api/src/Storage/Base.php b/api/src/Storage/Base.php index 45b4af6a8e..c4677416f6 100644 --- a/api/src/Storage/Base.php +++ b/api/src/Storage/Base.php @@ -966,6 +966,8 @@ class Base $num_rows = 0; // as spec. in max_matches in the user-prefs if (is_array($start)) list($start,$num_rows) = $start+[null,null]; + $order_by = self::sanitizeOrderBy($order_by); + // fix GROUP BY clause to contain all non-aggregate selected columns if ($order_by && stripos($order_by,'GROUP BY') !== false) { @@ -1066,6 +1068,39 @@ class Base return $arr; } + + /** + * Sanitize (currently just remove) not understood ORDER BY clause + * + * @param string $fragment SQL fragment containing ORDER BY clause, could also contain GROUP BY etc + * @return string sanitized version of $_order_by + */ + static function sanitizeOrderBy(string $fragment) + { + // check fragment contains ORDER BY --> just operate on what's behind + if (stripos($fragment,'ORDER BY') !== false) + { + [$group_by, $order_by] = preg_split('/order +by +/i', $fragment); + } + // check fragment not just contain GROUP BY or HAVING --> nothing to do + elseif ($fragment === '' || stripos($fragment,'GROUP BY')!==false || stripos($fragment,'HAVING')!==false) + { + return $fragment; + } + // fragment is ORDER BY clause + else + { + $order_by = $fragment; + } + if (!preg_match_all("/(#?[a-zA-Z_.]+) *(<> *''|IS NULL|IS NOT NULL|& *\d+)? *(ASC|DESC)?(,|$)/ui", $order_by, $all_matches) || + $order_by !== implode('', $all_matches[0])) + { + //error_log(__METHOD__."(".json_encode($fragment).") REMOVED"); + return $group_by??''; + } + return $fragment; + } + /** * Parse an array of search criteria into something that can be passed on * to the DB diff --git a/infolog/inc/class.infolog_so.inc.php b/infolog/inc/class.infolog_so.inc.php index 64deb39adb..c8b6d29ba4 100644 --- a/infolog/inc/class.infolog_so.inc.php +++ b/infolog/inc/class.infolog_so.inc.php @@ -821,7 +821,7 @@ class infolog_so } } $sortbycf=''; - if (!empty($query['order']) && (preg_match('/^[a-z_0-9, ]+$/i',$query['order']) || stripos($query['order'],'#')!==FALSE ) && + if (!empty($query['order']) && preg_match('/^#?[a-z_0-9, ]+$/i',$query['order']) && (empty($query['sort']) || is_string($query['sort']) && preg_match('/^(DESC|ASC)$/i',$query['sort']))) { $order = array();