CRM_Contact_BAO_Query::searchQuery - Preserve $order until last minute (EVIL)
authorTim Otten <totten@civicrm.org>
Wed, 25 May 2016 01:01:18 +0000 (18:01 -0700)
committerTim Otten <totten@civicrm.org>
Wed, 25 May 2016 01:04:52 +0000 (18:04 -0700)
Building on https://gist.github.com/mollux/7ffeb63f55ba0ba6180968d6d36f4745

This function does a lot of string-analysis on `$order`, and proper escaping
breaks this. However, the CRM_Utils_Type::escape() is quite clever about
validating and applying escaping.

CRM/Contact/BAO/Query.php

index 235b3713ed8839985a716a4a731c8555777014f5..c0d45a0111b084ba9206e8e93cc88dd0fc1177d3 100644 (file)
@@ -4570,18 +4570,20 @@ civicrm_relationship.is_permission_a_b = 0
           else {
             $orderBy = trim($sort->orderBy());
           }
+          // Deliberately remove the backticks again, as they mess up the evil
+          // string munging below. This balanced by re-escaping before use.
+          $orderBy = str_replace('`', '', $orderBy);
+
           if (!empty($orderBy)) {
             // this is special case while searching for
             // change log CRM-1718
-            if (preg_match('/`sort_name`/i', $orderBy)) {
-              $orderBy = str_replace('`sort_name`', '`contact_a`.`sort_name`', $orderBy);
+            if (preg_match('/sort_name/i', $orderBy)) {
+              $orderBy = str_replace('sort_name', 'contact_a.sort_name', $orderBy);
             }
 
-            $orderBy = CRM_Utils_Type::escape($orderBy, 'String');
             $order = " ORDER BY $orderBy";
 
             if ($sortOrder) {
-              $sortOrder = CRM_Utils_Type::escape($sortOrder, 'String');
               $order .= " $sortOrder";
             }
 
@@ -4641,6 +4643,13 @@ civicrm_relationship.is_permission_a_b = 0
         }
       }
 
+      // The above code relies on crazy brittle string manipulation of a peculiarly-encoded ORDER BY
+      // clause. But this magic helper which forgivingly reescapes ORDER BY.
+      // Note: $sortByChar implies that $order was hard-coded/trusted, so it can do funky things.
+      if ($order && !$sortByChar) {
+        $order = ' ORDER BY ' . CRM_Utils_Type::escape(preg_replace('/^\s*ORDER BY\s*/', '', $order), 'MysqlOrderBy');
+      }
+
       if ($rowCount > 0 && $offset >= 0) {
         $offset = CRM_Utils_Type::escape($offset, 'Int');
         $rowCount = CRM_Utils_Type::escape($rowCount, 'Int');