Added extra Mysql types and rules + tests similar to 4.7
authorMattias Michaux <mattias.michaux@gmail.com>
Tue, 3 May 2016 20:53:59 +0000 (22:53 +0200)
committerMattias Michaux <mattias.michaux@gmail.com>
Tue, 3 May 2016 20:53:59 +0000 (22:53 +0200)
CRM/Utils/Rule.php
CRM/Utils/Type.php
tests/phpunit/CRM/Utils/TypeTest.php

index 9c59136f9ab3bbd505794fc88b5e5affa6a23650..32cbd75919da55972e4f7f2519720a122990ded8 100644 (file)
@@ -103,6 +103,9 @@ class CRM_Utils_Rule {
     }
 
     // Ensure it only contains valid characters (alphanumeric and underscores).
+    //
+    // MySQL permits column names that don't match this (eg containing spaces),
+    // but CiviCRM won't create those ...
     if (!preg_match('/^\w{1,64}(\.\w{1,64})?$/i', $str)) {
       return FALSE;
     }
@@ -126,6 +129,25 @@ class CRM_Utils_Rule {
     return TRUE;
   }
 
+  /**
+   * Validate that a string is valid order by clause.
+   *
+   * @param $str
+   * @return bool
+   */
+  public static function mysqlOrderBy($str) {
+    // Making a regex for a comma separated list is quite hard and not readable
+    // at all, so we split and loop over.
+    $parts = explode(',', $str);
+    foreach ($parts as $part) {
+      if (!preg_match('/^((\w{1,64})((\.)(\w{1,64}))?( (asc|desc))?)$/i', trim($part))) {
+        return FALSE;
+      }
+    }
+
+    return TRUE;
+  }
+
   /**
    * @param $str
    *
index e299b6eae0dc5902ca7648b266fa9d5502a7c934..730797d64e7b1a20829e82f9fca9e946d5950ed8 100644 (file)
@@ -269,7 +269,17 @@ class CRM_Utils_Type {
 
       case 'MysqlOrderByDirection':
         if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
-          return $data;
+          return strtolower($data);
+        }
+        break;
+
+      case 'MysqlOrderBy':
+        if (CRM_Utils_Rule::mysqlOrderBy($data)) {
+          $parts = explode(',', $data);
+          foreach ($parts as &$part) {
+            $part = preg_replace_callback('/(?:([\w]+)(?:(?:\.)([\w]+))?(?: (asc|desc))?)/i', array('CRM_Utils_Type', 'mysqlOrderByCallback'), trim($part));
+          }
+          return implode(', ', $parts);
         }
         break;
 
@@ -376,6 +386,22 @@ class CRM_Utils_Type {
         }
         break;
 
+      case 'MysqlColumnName':
+        if (CRM_Utils_Rule::mysqlColumnName($data)) {
+          return $data;
+        }
+        break;
+      case 'MysqlOrderByDirection':
+        if (CRM_Utils_Rule::mysqlOrderByDirection($data)) {
+          return strtolower($data);
+        }
+        break;
+      case 'MysqlOrderBy':
+        if (CRM_Utils_Rule::mysqlOrderBy($data)) {
+          return $data;
+        }
+        break;
+
       default:
         CRM_Core_Error::fatal("Cannot recognize $type for $data");
         break;
@@ -389,4 +415,24 @@ class CRM_Utils_Type {
     return NULL;
   }
 
+  /**
+   * preg_replace_callback for MysqlOrderBy escape.
+   */
+  public static function mysqlOrderByCallback($matches) {
+    $output = '';
+    // Column or table name.
+    if (isset($matches[1])) {
+      $output .= '`' . $matches[1] . '`';
+    }
+    // Column name in case there is a table.
+    if (isset($matches[2]) && $matches[2]) {
+      $output .= '.`' . $matches[2] . '`';
+    }
+    // Sort order.
+    if (isset($matches[3]) && $matches[3]) {
+      $output .= ' ' . $matches[3];
+    }
+    return $output;
+  }
+
 }
index e9235565cc67663419292cbec07e8a17d1f3898e..9c46b671b22ee4939c3a135d1817e75868bd78a7 100644 (file)
@@ -38,6 +38,21 @@ class CRM_Utils_TypeTest extends CiviUnitTestCase {
       array(-10, 'Positive', NULL),
       array('-10', 'Positive', NULL),
       array('-10foo', 'Positive', NULL),
+      array('civicrm_column_name', 'MysqlColumnName', 'civicrm_column_name'),
+      array('table.civicrm_column_name', 'MysqlColumnName', 'table.civicrm_column_name'),
+      array('table.civicrm_column_name.toomanydots', 'MysqlColumnName', NULL),
+      array('invalid-column-name', 'MysqlColumnName', NULL),
+      array('column_name, sleep(5)', 'MysqlColumnName', NULL),
+      array(str_repeat('a', 64), 'MysqlColumnName', str_repeat('a', 64)),
+      array(str_repeat('a', 65), 'MysqlColumnName', NULL),
+      array(str_repeat('a', 64) . '.' . str_repeat('a', 64), 'MysqlColumnName', str_repeat('a', 64) . '.' . str_repeat('a', 64)),
+      array(str_repeat('a', 64) . '.' . str_repeat('a', 65), 'MysqlColumnName', NULL),
+      array(str_repeat('a', 65) . '.' . str_repeat('a', 64), 'MysqlColumnName', NULL),
+      array('asc', 'MysqlOrderByDirection', 'asc'),
+      array('DESC', 'MysqlOrderByDirection', 'desc'),
+      array('DESCc', 'MysqlOrderByDirection', NULL),
+      array('table.civicrm_column_name desc', 'MysqlOrderBy', 'table.civicrm_column_name desc'),
+      array('table.civicrm_column_name desc,other_column, another_column desc', 'MysqlOrderBy', 'table.civicrm_column_name desc,other_column, another_column desc'),
     );
   }
 
@@ -76,6 +91,16 @@ class CRM_Utils_TypeTest extends CiviUnitTestCase {
       array('-3', 'ContactReference', NULL),
       // Escape function is meant for sql, not xss
       array('<p onclick="alert(\'xss\');">Hello</p>', 'Memo', '<p onclick=\\"alert(\\\'xss\\\');\\">Hello</p>'),
+      array('civicrm_column_name', 'MysqlColumnName', '`civicrm_column_name`'),
+      array('table.civicrm_column_name', 'MysqlColumnName', '`table`.`civicrm_column_name`'),
+      array('table.civicrm_column_name.toomanydots', 'MysqlColumnName', NULL),
+      array('invalid-column-name', 'MysqlColumnName', NULL),
+      array('column_name, sleep(5)', 'MysqlColumnName', NULL),
+      array('asc', 'MysqlOrderByDirection', 'asc'),
+      array('DESC', 'MysqlOrderByDirection', 'desc'),
+      array('DESCc', 'MysqlOrderByDirection', NULL),
+      array('table.civicrm_column_name desc', 'MysqlOrderBy', '`table`.`civicrm_column_name` desc'),
+      array('table.civicrm_column_name desc,other_column,another_column desc', 'MysqlOrderBy', '`table`.`civicrm_column_name` desc, `other_column`, `another_column` desc'),
     );
   }