This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch 3.6.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 57879b1d1eea5a8b5f9fb367cd7ffa6ef6cde860
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Mon Sep 7 15:13:32 2026 +0100

    Put a max length on the Fiql search term and fix performance parsing issue 
(#3443)
    
    (cherry picked from commit 913f72c74cfdcae0132c64ae36b7851921c17bc0)
---
 .../cxf/jaxrs/ext/search/SearchContextImpl.java    |   4 +-
 .../cxf/jaxrs/ext/search/fiql/FiqlParser.java      |  31 +++++++
 .../jaxrs/ext/search/SearchContextImplTest.java    |   8 ++
 .../jaxrs/ext/search/fiql/FiqlParserReDoSTest.java | 102 +++++++++++++++++++++
 4 files changed, 144 insertions(+), 1 deletion(-)

diff --git 
a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/SearchContextImpl.java
 
b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/SearchContextImpl.java
index 4318632d8c3..70076136cf5 100644
--- 
a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/SearchContextImpl.java
+++ 
b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/SearchContextImpl.java
@@ -186,7 +186,7 @@ public class SearchContextImpl implements SearchContext {
 
         final Map<String, String> props;
         if (parserProperties == null) {
-            props = new HashMap<>(6);
+            props = new HashMap<>(7);
             props.put(SearchUtils.DATE_FORMAT_PROPERTY,
                       
(String)message.getContextualProperty(SearchUtils.DATE_FORMAT_PROPERTY));
             props.put(SearchUtils.TIMEZONE_SUPPORT_PROPERTY,
@@ -200,6 +200,8 @@ public class SearchContextImpl implements SearchContext {
                       
(String)message.getContextualProperty(FiqlParser.SUPPORT_SINGLE_EQUALS));
             props.put(FiqlParser.MAX_PARENTHESIS_DEPTH,
                       
(String)message.getContextualProperty(FiqlParser.MAX_PARENTHESIS_DEPTH));
+            props.put(FiqlParser.MAX_EXPRESSION_LENGTH,
+                      
(String)message.getContextualProperty(FiqlParser.MAX_EXPRESSION_LENGTH));
         } else {
             props = parserProperties;
         }
diff --git 
a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java
 
b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java
index a71839402d0..19563717839 100644
--- 
a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java
+++ 
b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java
@@ -70,10 +70,15 @@ public class FiqlParser<T> extends 
AbstractSearchConditionParser<T> {
      * the request thread. Must be a positive integer; the default is 64.
      */
     public static final String MAX_PARENTHESIS_DEPTH = 
"fiql.max.parenthesis.depth";
+    /**
+    * Context property limiting the length of a FIQL expression. The default 
is 4 KiB.
+     */
+    public static final String MAX_EXPRESSION_LENGTH = 
"fiql.max.expression.length";
     public static final String EXTENSION_COUNT = "count";
     protected static final String EXTENSION_COUNT_OPEN = EXTENSION_COUNT + "(";
 
     private static final int DEFAULT_MAX_PARENTHESIS_DEPTH = 64;
+    private static final int DEFAULT_MAX_EXPRESSION_LENGTH = 4 * 1024;
     private static final Map<String, ConditionType> OPERATORS_MAP;
     private static final Pattern COMPARATORS_PATTERN;
     private static final Pattern COMPARATORS_PATTERN_SINGLE_EQUALS;
@@ -110,6 +115,7 @@ public class FiqlParser<T> extends 
AbstractSearchConditionParser<T> {
     protected Pattern comparatorsPattern = COMPARATORS_PATTERN;
 
     private final int maxParenthesisDepth;
+    private final int maxExpressionLength;
 
     /**
      * Creates FIQL parser.
@@ -146,6 +152,8 @@ public class FiqlParser<T> extends 
AbstractSearchConditionParser<T> {
         super(tclass, contextProperties, beanProperties);
 
         this.maxParenthesisDepth = 
parseMaxParenthesisDepth(this.contextProperties.get(MAX_PARENTHESIS_DEPTH));
+        this.maxExpressionLength = parseMaxExpressionLength(
+            this.contextProperties.get(MAX_EXPRESSION_LENGTH));
 
         if 
(PropertyUtils.isTrue(this.contextProperties.get(SUPPORT_SINGLE_EQUALS))) {
             operatorsMap = new HashMap<>(operatorsMap);
@@ -172,6 +180,24 @@ public class FiqlParser<T> extends 
AbstractSearchConditionParser<T> {
         return depth;
     }
 
+    private static int parseMaxExpressionLength(String value) {
+        if (value == null || value.trim().isEmpty()) {
+            return DEFAULT_MAX_EXPRESSION_LENGTH;
+        }
+        final int length;
+        try {
+            length = Integer.parseInt(value.trim());
+        } catch (NumberFormatException ex) {
+            throw new IllegalArgumentException(MAX_EXPRESSION_LENGTH
+                + " must be a positive integer, got: " + value, ex);
+        }
+        if (length < 1) {
+            throw new IllegalArgumentException(MAX_EXPRESSION_LENGTH
+                + " must be a positive integer, got: " + value);
+        }
+        return length;
+    }
+
     /**
      * Parses expression and builds search filter. Names used in FIQL 
expression are names of getters/setters
      * in type T.
@@ -196,6 +222,11 @@ public class FiqlParser<T> extends 
AbstractSearchConditionParser<T> {
      */
     @Override
     public SearchCondition<T> parse(String fiqlExpression) throws 
SearchParseException {
+        if (fiqlExpression.length() > maxExpressionLength) {
+            throw new SearchParseException("Exceeded the maximum FIQL 
expression length of "
+                + maxExpressionLength + "; the limit can be adjusted with the "
+                + MAX_EXPRESSION_LENGTH + " property");
+        }
         ASTNode<T> ast = parseAndsOrsBrackets(fiqlExpression, 0);
         return ast.build();
     }
diff --git 
a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/SearchContextImplTest.java
 
b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/SearchContextImplTest.java
index f30c9ea7b0c..b3c6b1c9e80 100644
--- 
a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/SearchContextImplTest.java
+++ 
b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/SearchContextImplTest.java
@@ -76,6 +76,14 @@ public class SearchContextImplTest {
         assertNotNull(new SearchContextImpl(m).getCondition(Book.class));
     }
 
+    @Test(expected = SearchParseException.class)
+    public void testMaxExpressionLengthPropertyIsHonoured() {
+        Message m = new MessageImpl();
+        m.put(FiqlParser.MAX_EXPRESSION_LENGTH, "10");
+        m.put(Message.QUERY_STRING, "_s=name==12345");
+        new SearchContextImpl(m).getCondition(Book.class);
+    }
+
     @Test
     public void testPlainQuery2() {
         Message m = new MessageImpl();
diff --git 
a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserReDoSTest.java
 
b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserReDoSTest.java
new file mode 100644
index 00000000000..efdadbeb718
--- /dev/null
+++ 
b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserReDoSTest.java
@@ -0,0 +1,102 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.ext.search.fiql;
+
+import java.util.Collections;
+
+import org.apache.cxf.jaxrs.ext.search.SearchParseException;
+
+import org.junit.Test;
+
+public class FiqlParserReDoSTest {
+    @Test(timeout = 2000)
+    public void testLongExpressionWithoutComparatorIsRejectedQuickly() {
+        StringBuilder expression = new StringBuilder(32768);
+        for (int i = 0; i < 32768; i++) {
+            expression.append('a');
+        }
+        try {
+            new FiqlParser<>(Bean.class).parse(expression.toString());
+        } catch (SearchParseException ex) {
+            return;
+        }
+        throw new AssertionError("An expression without a comparator must be 
rejected");
+    }
+
+    @Test
+    public void testComparatorsRemainSupported() throws SearchParseException {
+        FiqlParser<Bean> parser = new FiqlParser<>(Bean.class);
+        parser.parse("name==value");
+        parser = new FiqlParser<>(Bean.class,
+            Collections.singletonMap(FiqlParser.SUPPORT_SINGLE_EQUALS, 
"true"));
+        parser.parse("name=first");
+    }
+
+    @Test
+    public void testConfiguredExpressionLengthLimit() throws 
SearchParseException {
+        FiqlParser<Bean> parser = new FiqlParser<>(Bean.class,
+            Collections.singletonMap(FiqlParser.MAX_EXPRESSION_LENGTH, "10"));
+        parser.parse("name==1234");
+        try {
+            parser.parse("name==12345");
+        } catch (SearchParseException ex) {
+            return;
+        }
+        throw new AssertionError("An expression over the configured length 
must be rejected");
+    }
+
+    @Test
+    public void testDefaultExpressionLengthLimitIsFourKiB() throws 
SearchParseException {
+        FiqlParser<Bean> parser = new FiqlParser<>(Bean.class);
+        parser.parse(expressionOfLength(4096));
+        try {
+            parser.parse(expressionOfLength(4097));
+        } catch (SearchParseException ex) {
+            return;
+        }
+        throw new AssertionError("The default expression length limit must be 
4 KiB");
+    }
+
+    private static String expressionOfLength(int length) {
+        StringBuilder expression = new StringBuilder(length);
+        expression.append("name==");
+        while (expression.length() < length) {
+            expression.append('a');
+        }
+        return expression.substring(0, length);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidExpressionLengthLimitRejected() {
+        new FiqlParser<>(Bean.class,
+            Collections.singletonMap(FiqlParser.MAX_EXPRESSION_LENGTH, "0"));
+    }
+
+    public static class Bean {
+        private String name;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+}
\ No newline at end of file

Reply via email to