From 4bcfc8a38c5490f5eeb98e90cc589024c2c4ff1e Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Mon, 28 Sep 2015 22:04:55 -0500
Subject: [PATCH] search: Try harder to match MySQL full text boolean query

As currently written, if a full text query is attempted in BOOLEAN mode and
the query cannot be compiled by MySQL, it causes an OrmException and a
crash. This patch adds a more straight-forward and comprehensive
grammer-based approach to checking for valid boolean syntax.
---
 include/class.search.php | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/include/class.search.php b/include/class.search.php
index 07f068894..6cac6d7e4 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -334,8 +334,31 @@ class MysqlSearchBackend extends SearchBackend {
         // will ensure proper placement of operators, whitespace, and quotes
         // in an effort to avoid crashing the query at MySQL
         $query = $this->quote($query);
-        if (preg_match('/(?=(?:^|\s)[()"+<>~-])\s*(?:[(+<>~-]*(\w+[*]?|"[^"]+")[)]?(\s+|$))+$|\w[*]/u', $query, $T = array()))
+
+        // According to the MySQL full text boolean mode, this grammar is
+        // assumed:
+        // see http://dev.mysql.com/doc/refman/5.6/en/fulltext-boolean.html
+        //
+        // PREOP    = [<>~+-]
+        // POSTOP   = [*]
+        // WORD     = [\w][\w-]*
+        // TERM     = PREOP? WORD POSTOP?
+        // QWORD    = " [^"]+ "
+        // PARENS   = \( { { TERM | QWORD } { \s+ { TERM | QWORD } }+ } \)
+        // EXPR     = { PREOP? PARENS | TERM | QWORD }
+        // BOOLEAN  = EXPR { \s+ EXPR }*
+        //
+        // Changing '{' for (?: and '}' for ')', collapsing whitespace, we
+        // have this regular expression
+        $BOOLEAN = '(?:[<>~+-]?\((?:(?:[<>~+-]?[\w][\w-]*[*]?|"[^"]+")(?:\s+(?:[<>~+-]?[\w][\w-]*[*]?|"[^"]+"))+)\)|[<>~+-]?[\w][\w-]*[*]?|"[^"]+")(?:\s+(?:[<>~+-]?\((?:(?:[<>~+-]?[\w][\w-]*[*]?|"[^"]+")(?:\s+(?:[<>~+-]?[\w][\w-]*[*]?|"[^"]+"))+)\)|[<>~+-]?[\w][\w-]*[*]?|"[^"]+"))*';
+
+        // Require the use of at least one operator and conform to the
+        // boolean mode grammar
+        if (preg_match('`(^|\s)["()<>~+-]`u', $query, $T = array())
+            && preg_match("`^{$BOOLEAN}$`u", $query, $T = array())
+        ) {
             $mode = ' IN BOOLEAN MODE';
+        }
         #elseif (count(explode(' ', $query)) == 1)
         #    $mode = ' WITH QUERY EXPANSION';
         $search = 'MATCH (Z1.title, Z1.content) AGAINST ('.db_input($query).$mode.')';
-- 
GitLab