diff --git a/src/main/java/org/codelibs/fess/Constants.java b/src/main/java/org/codelibs/fess/Constants.java index 881809e8e50cd50e9f022050de256918a8134f15..2efb393792255a78631ca2982ca6cc691b0486a6 100644 --- a/src/main/java/org/codelibs/fess/Constants.java +++ b/src/main/java/org/codelibs/fess/Constants.java @@ -251,7 +251,7 @@ public class Constants extends CoreLibConstants { public static final String FTP = "FTP"; public static final String[] RESERVED = - { "+", "-", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "~", "*", "?", "\\", ";", ":", "/" }; + { "\\", "+", "-", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "~", "*", "?", ";", ":", "/" }; public static final Pattern LUCENE_FIELD_RESERVED_PATTERN = Pattern.compile("([+\\-!\\(\\){}\\[\\]^\"~\\\\:\\p{Zs}]|(&&)|(\\|\\|))"); // "*", "?", diff --git a/src/main/java/org/codelibs/fess/helper/SearchHelper.java b/src/main/java/org/codelibs/fess/helper/SearchHelper.java index f57d607b3828de1ae77f4261a69787feaf2ec1b5..a9a6d445c075bb70e67246e7ce6e838f43506c26 100644 --- a/src/main/java/org/codelibs/fess/helper/SearchHelper.java +++ b/src/main/java/org/codelibs/fess/helper/SearchHelper.java @@ -49,6 +49,7 @@ import org.codelibs.fess.entity.SearchRequestParams; import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType; import org.codelibs.fess.es.client.SearchEngineClient.SearchConditionBuilder; import org.codelibs.fess.es.client.SearchEngineClientException; +import org.codelibs.fess.exception.InvalidQueryException; import org.codelibs.fess.mylasta.action.FessUserBean; import org.codelibs.fess.mylasta.direction.FessConfig; import org.codelibs.fess.util.BooleanFunction; @@ -81,39 +82,18 @@ public class SearchHelper { request.setAttribute(Constants.REQUEST_QUERIES, params.getQuery()); }); - final int pageStart = params.getStartPosition(); - final int pageSize = params.getPageSize(); - final String sortField = params.getSort(); - final String query; - if (StringUtil.isBlank(sortField)) { - query = ComponentUtil.getQueryStringBuilder().params(params).build(); - } else { - query = ComponentUtil.getQueryStringBuilder().params(params).build() + " sort:" + sortField; + String query = ComponentUtil.getQueryStringBuilder().params(params).sortField(params.getSort()).build(); + List<Map<String, Object>> documentItems; + try { + documentItems = searchInternal(query, params, userBean); + } catch (final InvalidQueryException e) { + if (logger.isDebugEnabled()) { + logger.debug("Invalid query: {}", query, e); + } + query = ComponentUtil.getQueryStringBuilder().params(params).sortField(params.getSort()).escape(true).build(); + documentItems = searchInternal(query, params, userBean); } - final FessConfig fessConfig = ComponentUtil.getFessConfig(); - final QueryHelper queryHelper = ComponentUtil.getQueryHelper(); - final List<Map<String, Object>> documentItems = - ComponentUtil.getSearchEngineClient().search(fessConfig.getIndexDocumentSearchIndex(), searchRequestBuilder -> { - queryHelper.processSearchPreference(searchRequestBuilder, userBean, query); - return SearchConditionBuilder.builder(searchRequestBuilder).query(query).offset(pageStart).size(pageSize) - .facetInfo(params.getFacetInfo()).geoInfo(params.getGeoInfo()).highlightInfo(params.getHighlightInfo()) - .similarDocHash(params.getSimilarDocHash()).responseFields(queryHelper.getResponseFields()) - .searchRequestType(params.getType()).trackTotalHits(params.getTrackTotalHits()).build(); - }, (searchRequestBuilder, execTime, searchResponse) -> { - searchResponse.ifPresent(r -> { - if (r.getTotalShards() != r.getSuccessfulShards() && fessConfig.isQueryTimeoutLogging()) { - // partial results - final StringBuilder buf = new StringBuilder(1000); - buf.append("[SEARCH TIMEOUT] {\"exec_time\":").append(execTime)// - .append(",\"request\":").append(searchRequestBuilder.toString())// - .append(",\"response\":").append(r.toString()).append('}'); - logger.warn(buf.toString()); - } - }); - final QueryResponseList queryResponseList = ComponentUtil.getQueryResponseList(); - queryResponseList.init(searchResponse, pageStart, pageSize); - return queryResponseList; - }); + data.setDocumentItems(documentItems); // search @@ -142,7 +122,7 @@ public class SearchHelper { } data.setExecTime(execTime); - final String queryId = queryHelper.generateId(); + final String queryId = ComponentUtil.getQueryHelper().generateId(); data.setPageSize(queryResponseList.getPageSize()); data.setCurrentPageNumber(queryResponseList.getCurrentPageNumber()); @@ -160,10 +140,12 @@ public class SearchHelper { data.setRequestedTime(requestedTime); data.setQueryId(queryId); + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + // search log if (fessConfig.isSearchLog()) { - ComponentUtil.getSearchLogHelper().addSearchLog(params, DfTypeUtil.toLocalDateTime(requestedTime), queryId, query, pageStart, - pageSize, queryResponseList); + ComponentUtil.getSearchLogHelper().addSearchLog(params, DfTypeUtil.toLocalDateTime(requestedTime), queryId, query, + params.getStartPosition(), params.getPageSize(), queryResponseList); } // favorite @@ -173,6 +155,34 @@ public class SearchHelper { } + protected List<Map<String, Object>> searchInternal(final String query, final SearchRequestParams params, + final OptionalThing<FessUserBean> userBean) { + final FessConfig fessConfig = ComponentUtil.getFessConfig(); + final QueryHelper queryHelper = ComponentUtil.getQueryHelper(); + return ComponentUtil.getSearchEngineClient().search(fessConfig.getIndexDocumentSearchIndex(), searchRequestBuilder -> { + queryHelper.processSearchPreference(searchRequestBuilder, userBean, query); + return SearchConditionBuilder.builder(searchRequestBuilder).query(query).offset(params.getStartPosition()) + .size(params.getPageSize()).facetInfo(params.getFacetInfo()).geoInfo(params.getGeoInfo()) + .highlightInfo(params.getHighlightInfo()).similarDocHash(params.getSimilarDocHash()) + .responseFields(queryHelper.getResponseFields()).searchRequestType(params.getType()) + .trackTotalHits(params.getTrackTotalHits()).build(); + }, (searchRequestBuilder, execTime, searchResponse) -> { + searchResponse.ifPresent(r -> { + if (r.getTotalShards() != r.getSuccessfulShards() && fessConfig.isQueryTimeoutLogging()) { + // partial results + final StringBuilder buf = new StringBuilder(1000); + buf.append("[SEARCH TIMEOUT] {\"exec_time\":").append(execTime)// + .append(",\"request\":").append(searchRequestBuilder.toString())// + .append(",\"response\":").append(r.toString()).append('}'); + logger.warn(buf.toString()); + } + }); + final QueryResponseList queryResponseList = ComponentUtil.getQueryResponseList(); + queryResponseList.init(searchResponse, params.getStartPosition(), params.getPageSize()); + return queryResponseList; + }); + } + public long scrollSearch(final SearchRequestParams params, final BooleanFunction<Map<String, Object>> cursor, final OptionalThing<FessUserBean> userBean) { LaRequestUtil.getOptionalRequest().ifPresent(request -> { @@ -181,13 +191,7 @@ public class SearchHelper { }); final int pageSize = params.getPageSize(); - final String sortField = params.getSort(); - final String query; - if (StringUtil.isBlank(sortField)) { - query = ComponentUtil.getQueryStringBuilder().params(params).build(); - } else { - query = ComponentUtil.getQueryStringBuilder().params(params).build() + " sort:" + sortField; - } + final String query = ComponentUtil.getQueryStringBuilder().params(params).sortField(params.getSort()).build(); final FessConfig fessConfig = ComponentUtil.getFessConfig(); return ComponentUtil.getSearchEngineClient().<Map<String, Object>> scrollSearch(fessConfig.getIndexDocumentSearchIndex(), searchRequestBuilder -> { diff --git a/src/main/java/org/codelibs/fess/util/QueryStringBuilder.java b/src/main/java/org/codelibs/fess/util/QueryStringBuilder.java index 8bbbf4c2f101a326b9b98d2755ff0da34f211bb3..30c7f12f3deace5e550a15448fbeb550967a0c1b 100644 --- a/src/main/java/org/codelibs/fess/util/QueryStringBuilder.java +++ b/src/main/java/org/codelibs/fess/util/QueryStringBuilder.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.stream.Collectors; import org.codelibs.core.lang.StringUtil; +import org.codelibs.fess.Constants; import org.codelibs.fess.entity.SearchRequestParams; import org.codelibs.fess.helper.RelatedQueryHelper; import org.codelibs.fess.mylasta.direction.FessConfig; @@ -36,6 +37,10 @@ public class QueryStringBuilder { private SearchRequestParams params; + private boolean escape = false; + + private String sortField; + protected String quote(final String value) { if (value.split("\\s").length > 1) { return new StringBuilder().append('"').append(value.replace('"', ' ')).append('"').toString(); @@ -43,6 +48,19 @@ public class QueryStringBuilder { return value; } + protected String escapeQuery(final String value) { + if (!escape) { + return value; + } + + String newValue = value; + for (int i = 0; i < Constants.RESERVED.length; i++) { + final String replacement = Constants.RESERVED[i].replaceAll("(.)", "\\\\$1"); + newValue = newValue.replace(Constants.RESERVED[i], replacement); + } + return newValue; + } + public String build() { final FessConfig fessConfig = ComponentUtil.getFessConfig(); final int maxQueryLength = fessConfig.getQueryMaxLengthAsInteger(); @@ -50,7 +68,7 @@ public class QueryStringBuilder { final String query = buildBaseQuery(); if (StringUtil.isNotBlank(query)) { - queryBuf.append(query); + queryBuf.append(escapeQuery(query)); } stream(params.getExtraQueries()) @@ -80,7 +98,11 @@ public class QueryStringBuilder { } })); - return queryBuf.toString().trim(); + String baseQuery = queryBuf.toString().trim(); + if (StringUtil.isBlank(sortField)) { + return baseQuery; + } + return baseQuery + " sort:" + sortField; } protected void appendQuery(final StringBuilder queryBuf, final String query) { @@ -183,4 +205,14 @@ public class QueryStringBuilder { this.params = params; return this; } + + public QueryStringBuilder sortField(final String sortField) { + this.sortField = sortField; + return this; + } + + public QueryStringBuilder escape(final boolean escape) { + this.escape = escape; + return this; + } } diff --git a/src/test/java/org/codelibs/fess/util/QueryStringBuilderTest.java b/src/test/java/org/codelibs/fess/util/QueryStringBuilderTest.java index 19c38bfcf9ebff11b2b8285e88a508e1f6e6d254..e7c95d48ea4d386858dbb160efe7c73cb210f85a 100644 --- a/src/test/java/org/codelibs/fess/util/QueryStringBuilderTest.java +++ b/src/test/java/org/codelibs/fess/util/QueryStringBuilderTest.java @@ -28,7 +28,7 @@ import org.codelibs.fess.unit.UnitFessTestCase; public class QueryStringBuilderTest extends UnitFessTestCase { public void test_query() { - assertEquals("", getQuery("", new String[0], Collections.emptyMap(), Collections.emptyMap())); + assertEquals("", getQuery("", new String[0], Collections.emptyMap(), Collections.emptyMap(), false)); } public void test_conditions_q() { @@ -69,16 +69,26 @@ public class QueryStringBuilderTest extends UnitFessTestCase { assertEquals("NOT aaa NOT bbb", getAsQuery("111", Collections.singletonMap(k, new String[] { "aaa bbb" }))); } + public void test_escape() { + assertEquals("\\/", getQuery("/", new String[0], Collections.emptyMap(), Collections.emptyMap(), true)); + assertEquals("aaa\\/bbb", getQuery("aaa/bbb", new String[0], Collections.emptyMap(), Collections.emptyMap(), true)); + assertEquals("\\\\", getQuery("\\", new String[0], Collections.emptyMap(), Collections.emptyMap(), true)); + assertEquals("\\\\\\\\", getQuery("\\\\", new String[0], Collections.emptyMap(), Collections.emptyMap(), true)); + assertEquals("aaa \\&\\& bbb", getQuery("aaa && bbb", new String[0], Collections.emptyMap(), Collections.emptyMap(), true)); + assertEquals("\\\\\\+\\-\\&\\&\\|\\|\\!\\(\\)\\{\\}\\[\\]\\^\\~\\*\\?\\;\\:\\/", + getQuery("\\+-&&||!(){}[]^~*?;:/", new String[0], Collections.emptyMap(), Collections.emptyMap(), true)); + } + private String getAsQuery(final String query, final Map<String, String[]> conditions) { - return getQuery(query, new String[0], Collections.emptyMap(), conditions); + return getQuery(query, new String[0], Collections.emptyMap(), conditions, false); } private String getAsQuery(final Map<String, String[]> conditions) { - return getQuery("", new String[0], Collections.emptyMap(), conditions); + return getQuery("", new String[0], Collections.emptyMap(), conditions, false); } private String getQuery(final String query, final String[] extraQueries, final Map<String, String[]> fields, - final Map<String, String[]> conditions) { + final Map<String, String[]> conditions, final boolean escape) { return new QueryStringBuilder().params(new SearchRequestParams() { @Override @@ -156,6 +166,6 @@ public class QueryStringBuilderTest extends UnitFessTestCase { return new HighlightInfo(); } - }).build(); + }).escape(escape).build(); } }