@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Tighten Ferret query parsing of empty tokens and empty functions

Summary:
Ref T13509. Certain query tokens like `title:=""` are currently accepted by the parser but discarded, and have no impact on the query. This isn't desirable.

Instead, require that tokens making an assertion about field content must be nonempty.

Test Plan: Added unit tests, made them pass.

Maniphest Tasks: T13509

Differential Revision: https://secure.phabricator.com/D21106

+71 -29
+49 -20
src/applications/search/compiler/PhutilSearchQueryCompiler.php
··· 243 243 'Query contains unmatched double quotes.')); 244 244 } 245 245 246 - if ($mode == 'operator') { 247 - throw new PhutilSearchQueryCompilerSyntaxException( 248 - pht( 249 - 'Query contains operator ("%s") with no search term.', 250 - implode('', $current_operator))); 251 - } 246 + // If the input query has trailing space, like "a b ", we may exit the 247 + // parser without a final token. 248 + if ($current_function !== null || $current_operator || $current_token) { 249 + $token = array( 250 + 'operator' => $current_operator, 251 + 'quoted' => false, 252 + 'value' => $current_token, 253 + ); 252 254 253 - $token = array( 254 - 'operator' => $current_operator, 255 - 'quoted' => false, 256 - 'value' => $current_token, 257 - ); 255 + if ($enable_functions) { 256 + $token['function'] = $current_function; 257 + } 258 258 259 - if ($enable_functions) { 260 - $token['function'] = $current_function; 259 + $tokens[] = $token; 261 260 } 262 - 263 - $tokens[] = $token; 264 261 265 262 $results = array(); 266 263 foreach ($tokens as $token) { 267 264 $value = implode('', $token['value']); 268 265 $operator_string = implode('', $token['operator']); 269 - 270 - if (!strlen($value)) { 271 - continue; 272 - } 273 - 274 266 $is_quoted = $token['quoted']; 275 267 276 268 switch ($operator_string) { ··· 304 296 $operator_string)); 305 297 } 306 298 299 + if (!strlen($value)) { 300 + $require_value = $is_quoted; 301 + 302 + switch ($operator) { 303 + default: 304 + $require_value = true; 305 + break; 306 + } 307 + 308 + if ($require_value) { 309 + throw new PhutilSearchQueryCompilerSyntaxException( 310 + pht( 311 + 'Query contains a token ("%s") with no search term. Query '. 312 + 'tokens specify text to search for.', 313 + $this->getDisplayToken($token))); 314 + } 315 + } 316 + 307 317 $result = array( 308 318 'operator' => $operator, 309 319 'quoted' => $is_quoted, ··· 369 379 $close_quote = $this->operators[11]; 370 380 371 381 return $open_quote.$value.$close_quote; 382 + } 383 + 384 + private function getDisplayToken(array $token) { 385 + if (isset($token['function'])) { 386 + $function = $token['function'].':'; 387 + } else { 388 + $function = ''; 389 + } 390 + 391 + $operator_string = implode('', $token['operator']); 392 + 393 + $value = implode('', $token['value']); 394 + 395 + $is_quoted = $token['quoted']; 396 + if ($is_quoted) { 397 + $value = $this->quoteToken($value); 398 + } 399 + 400 + return sprintf('%s%s%s', $function, $operator_string, $value); 372 401 } 373 402 374 403 }
+22 -9
src/applications/search/compiler/__tests__/PhutilSearchQueryCompilerTestCase.php
··· 36 36 '"cat' => false, 37 37 'A"' => false, 38 38 'A"B"' => '+"A" +"B"', 39 + 40 + // Trailing whitespace should be discarded. 41 + 'a b ' => '+"a" +"b"', 42 + 43 + // Functions must have search text. 44 + '""' => false, 45 + '-' => false, 39 46 ); 40 47 41 48 $this->assertCompileQueries($tests); ··· 56 63 'cat -dog' => '+[cat] -[dog]', 57 64 ); 58 65 $this->assertCompileQueries($quote_tests, '+ -><()~*:[]&|'); 59 - 60 66 } 61 67 62 68 public function testCompileQueriesWithStemming() { ··· 133 139 '"'.$mao.'"' => array( 134 140 array(null, $op_and, $mao), 135 141 ), 142 + 'title:' => false, 143 + 'title:+' => false, 144 + 'title:+""' => false, 136 145 ); 137 146 138 147 $this->assertCompileFunctionQueries($function_tests); ··· 199 208 $compiler = id(new PhutilSearchQueryCompiler()) 200 209 ->setEnableFunctions(true); 201 210 202 - $tokens = $compiler->newTokens($input); 211 + try { 212 + $tokens = $compiler->newTokens($input); 203 213 204 - $result = array(); 205 - foreach ($tokens as $token) { 206 - $result[] = array( 207 - $token->getFunction(), 208 - $token->getOperator(), 209 - $token->getValue(), 210 - ); 214 + $result = array(); 215 + foreach ($tokens as $token) { 216 + $result[] = array( 217 + $token->getFunction(), 218 + $token->getOperator(), 219 + $token->getValue(), 220 + ); 221 + } 222 + } catch (PhutilSearchQueryCompilerSyntaxException $ex) { 223 + $result = false; 211 224 } 212 225 213 226 $this->assertEqual(