1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.helper;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.nio.charset.StandardCharsets;
22 import java.text.NumberFormat;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Base64;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Set;
34 import java.util.concurrent.ExecutionException;
35 import java.util.function.Consumer;
36 import java.util.stream.Collectors;
37 import java.util.zip.GZIPInputStream;
38 import java.util.zip.GZIPOutputStream;
39
40 import org.apache.logging.log4j.LogManager;
41 import org.apache.logging.log4j.Logger;
42 import org.codelibs.core.exception.IORuntimeException;
43 import org.codelibs.core.exception.InterruptedRuntimeException;
44 import org.codelibs.core.lang.StringUtil;
45 import org.codelibs.core.stream.StreamUtil;
46 import org.codelibs.fess.Constants;
47 import org.codelibs.fess.entity.QueryContext;
48 import org.codelibs.fess.entity.RequestParameter;
49 import org.codelibs.fess.entity.SearchRenderData;
50 import org.codelibs.fess.entity.SearchRequestParams;
51 import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
52 import org.codelibs.fess.exception.InvalidQueryException;
53 import org.codelibs.fess.exception.SearchQueryException;
54 import org.codelibs.fess.mylasta.action.FessUserBean;
55 import org.codelibs.fess.mylasta.direction.FessConfig;
56 import org.codelibs.fess.opensearch.client.SearchEngineClient.SearchConditionBuilder;
57 import org.codelibs.fess.opensearch.client.SearchEngineClientException;
58 import org.codelibs.fess.query.QueryFieldConfig;
59 import org.codelibs.fess.rank.fusion.RankFusionProcessor;
60 import org.codelibs.fess.util.BooleanFunction;
61 import org.codelibs.fess.util.ComponentUtil;
62 import org.codelibs.fess.util.QueryResponseList;
63 import org.dbflute.optional.OptionalEntity;
64 import org.dbflute.optional.OptionalThing;
65 import org.dbflute.util.DfTypeUtil;
66 import org.lastaflute.taglib.function.LaFunctions;
67 import org.lastaflute.web.util.LaRequestUtil;
68 import org.lastaflute.web.util.LaResponseUtil;
69 import org.opensearch.OpenSearchException;
70 import org.opensearch.action.DocWriteResponse.Result;
71 import org.opensearch.action.bulk.BulkRequestBuilder;
72 import org.opensearch.action.bulk.BulkResponse;
73 import org.opensearch.action.update.UpdateRequestBuilder;
74 import org.opensearch.action.update.UpdateResponse;
75 import org.opensearch.common.document.DocumentField;
76 import org.opensearch.index.query.BoolQueryBuilder;
77 import org.opensearch.index.query.QueryBuilders;
78
79 import com.fasterxml.jackson.databind.ObjectMapper;
80
81 import jakarta.servlet.http.Cookie;
82 import jakarta.servlet.http.HttpServletRequest;
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 public class SearchHelper {
100
101
102
103
104
105
106 private static final Logger logger = LogManager.getLogger(SearchHelper.class);
107
108
109
110
111
112
113 protected SearchRequestParamsRewriter[] searchRequestParamsRewriters = {};
114
115
116 protected ObjectMapper mapper = new ObjectMapper();;
117
118
119
120
121 public SearchHelper() {
122
123 }
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140 public void search(final SearchRequestParams searchRequestParams, final SearchRenderData data,
141 final OptionalThing<FessUserBean> userBean) {
142 final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
143 final long startTime = systemHelper.getCurrentTimeAsLong();
144 final long requestedTime = startTime;
145
146 final SearchRequestParams params = rewrite(searchRequestParams);
147
148 LaRequestUtil.getOptionalRequest().ifPresent(request -> {
149 request.setAttribute(Constants.REQUEST_LANGUAGES, params.getLanguages());
150 request.setAttribute(Constants.REQUEST_QUERIES, params.getQuery());
151 });
152
153 String query = ComponentUtil.getQueryStringBuilder().params(params).sortField(params.getSort()).build();
154 List<Map<String, Object>> documentItems;
155 try {
156 documentItems = searchInternal(query, params, userBean);
157 } catch (final InvalidQueryException e) {
158 if (logger.isDebugEnabled()) {
159 logger.debug("Invalid query: {}", query, e);
160 }
161 query = ComponentUtil.getQueryStringBuilder().params(params).sortField(params.getSort()).escape(true).build();
162 documentItems = searchInternal(query, params, userBean);
163 }
164
165 data.setDocumentItems(documentItems);
166
167
168 final QueryResponseList queryResponseList = (QueryResponseList) documentItems;
169 data.setFacetResponse(queryResponseList.getFacetResponse());
170
171 @SuppressWarnings("unchecked")
172 final Set<String> highlightQueries = (Set<String>) params.getAttribute(Constants.HIGHLIGHT_QUERIES);
173 if (highlightQueries != null) {
174 final StringBuilder buf = new StringBuilder(100);
175 highlightQueries.stream().forEach(q -> {
176 buf.append("&hq=").append(LaFunctions.u(q));
177 });
178 data.setAppendHighlightParams(buf.toString());
179 }
180
181 queryResponseList.setExecTime(systemHelper.getCurrentTimeAsLong() - startTime);
182 final NumberFormat nf = NumberFormat.getInstance(params.getLocale());
183 nf.setMaximumIntegerDigits(2);
184 nf.setMaximumFractionDigits(2);
185 String execTime;
186 try {
187 execTime = nf.format((double) queryResponseList.getExecTime() / 1000);
188 } catch (final Exception e) {
189 execTime = StringUtil.EMPTY;
190 }
191 data.setExecTime(execTime);
192
193 final String queryId = ComponentUtil.getQueryHelper().generateId();
194
195 data.setPageSize(queryResponseList.getPageSize());
196 data.setCurrentPageNumber(queryResponseList.getCurrentPageNumber());
197 data.setAllRecordCount(queryResponseList.getAllRecordCount());
198 data.setAllRecordCountRelation(queryResponseList.getAllRecordCountRelation());
199 data.setAllPageCount(queryResponseList.getAllPageCount());
200 data.setExistNextPage(queryResponseList.isExistNextPage());
201 data.setExistPrevPage(queryResponseList.isExistPrevPage());
202 data.setCurrentStartRecordNumber(queryResponseList.getCurrentStartRecordNumber());
203 data.setCurrentEndRecordNumber(queryResponseList.getCurrentEndRecordNumber());
204 data.setPageNumberList(queryResponseList.getPageNumberList());
205 data.setPartialResults(queryResponseList.isPartialResults());
206 data.setQueryTime(queryResponseList.getQueryTime());
207 data.setSearchQuery(query);
208 data.setRequestedTime(requestedTime);
209 data.setQueryId(queryId);
210
211 final FessConfig fessConfig = ComponentUtil.getFessConfig();
212
213
214 if (fessConfig.isSearchLog()) {
215 ComponentUtil.getSearchLogHelper()
216 .addSearchLog(params, DfTypeUtil.toLocalDateTime(requestedTime), queryId, query, params.getStartPosition(),
217 params.getPageSize(), queryResponseList);
218 }
219
220
221 if (fessConfig.isUserFavorite()) {
222 ComponentUtil.getUserInfoHelper().storeQueryId(queryId, documentItems);
223 }
224
225 }
226
227
228
229
230
231
232
233
234
235
236
237
238 protected List<Map<String, Object>> searchInternal(final String query, final SearchRequestParams params,
239 final OptionalThing<FessUserBean> userBean) {
240 final RankFusionProcessor rankFusionProcessor = ComponentUtil.getRankFusionProcessor();
241 final List<Map<String, Object>> documentItems = rankFusionProcessor.search(query, params, userBean);
242 if (documentItems instanceof final QueryResponseList queryResponseList) {
243 final FessConfig fessConfig = ComponentUtil.getFessConfig();
244 if (queryResponseList.getAllRecordCount() <= fessConfig.getQueryOrsearchMinHitCountAsInteger()) {
245 return LaRequestUtil.getOptionalRequest().map(request -> {
246 request.setAttribute(Constants.DEFAULT_QUERY_OPERATOR, "OR");
247 if (logger.isDebugEnabled()) {
248 logger.debug("The number of hits is {}<={}. Searching again with OR operator.",
249 queryResponseList.getAllRecordCount(), fessConfig.getQueryOrsearchMinHitCountAsInteger());
250 }
251 return rankFusionProcessor.search(query, params, userBean);
252 }).orElse(queryResponseList);
253 }
254 }
255 return documentItems;
256 }
257
258
259
260
261
262
263
264
265
266
267
268
269 public long scrollSearch(final SearchRequestParams params, final BooleanFunction<Map<String, Object>> cursor,
270 final OptionalThing<FessUserBean> userBean) {
271 LaRequestUtil.getOptionalRequest().ifPresent(request -> {
272 request.setAttribute(Constants.REQUEST_LANGUAGES, params.getLanguages());
273 request.setAttribute(Constants.REQUEST_QUERIES, params.getQuery());
274 });
275
276 final int pageSize = params.getPageSize();
277 final String query = ComponentUtil.getQueryStringBuilder().params(params).sortField(params.getSort()).build();
278 final FessConfig fessConfig = ComponentUtil.getFessConfig();
279 return ComponentUtil.getSearchEngineClient()
280 .<Map<String, Object>> scrollSearch(fessConfig.getIndexDocumentSearchIndex(), searchRequestBuilder -> {
281 final QueryHelper queryHelper = ComponentUtil.getQueryHelper();
282 final QueryFieldConfig queryFieldConfig = ComponentUtil.getQueryFieldConfig();
283 queryHelper.processSearchPreference(searchRequestBuilder, userBean, query);
284 return SearchConditionBuilder.builder(searchRequestBuilder)
285 .scroll()
286 .query(query)
287 .size(pageSize)
288 .responseFields(queryFieldConfig.getScrollResponseFields())
289 .searchRequestType(params.getType())
290 .build();
291 }, (searchResponse, hit) -> {
292 final Map<String, Object> docMap = new HashMap<>();
293 final Map<String, Object> source = hit.getSourceAsMap();
294 if (source != null) {
295 docMap.putAll(source);
296 }
297 final Map<String, DocumentField> fields = hit.getFields();
298 if (fields != null) {
299 docMap.putAll(fields.entrySet()
300 .stream()
301 .collect(Collectors.toMap(Entry::getKey, e -> (Object) e.getValue().getValues())));
302 }
303
304 final ViewHelper viewHelper = ComponentUtil.getViewHelper();
305 if (viewHelper != null && !docMap.isEmpty()) {
306 docMap.put(fessConfig.getResponseFieldContentTitle(), viewHelper.getContentTitle(docMap));
307 docMap.put(fessConfig.getResponseFieldContentDescription(), viewHelper.getContentDescription(docMap));
308 docMap.put(fessConfig.getResponseFieldUrlLink(), viewHelper.getUrlLink(docMap));
309 docMap.put(fessConfig.getResponseFieldSitePath(), viewHelper.getSitePath(docMap));
310 }
311
312 if (!docMap.containsKey(Constants.SCORE)) {
313 final float score = hit.getScore();
314 if (Float.isFinite(score)) {
315 docMap.put(Constants.SCORE, score);
316 }
317 }
318
319 docMap.put(fessConfig.getIndexFieldId(), hit.getId());
320 docMap.put(fessConfig.getIndexFieldVersion(), hit.getVersion());
321 docMap.put(fessConfig.getIndexFieldSeqNo(), hit.getSeqNo());
322 docMap.put(fessConfig.getIndexFieldPrimaryTerm(), hit.getPrimaryTerm());
323 return docMap;
324 }, cursor);
325 }
326
327
328
329
330
331
332
333
334 public long deleteByQuery(final HttpServletRequest request, final SearchRequestParams params) {
335 final String query = ComponentUtil.getQueryStringBuilder().params(params).build();
336
337 final QueryContext queryContext = ComponentUtil.getQueryHelper().build(params.getType(), query, context -> {
338 context.skipRoleQuery();
339 });
340 return ComponentUtil.getSearchEngineClient()
341 .deleteByQuery(ComponentUtil.getFessConfig().getIndexDocumentUpdateIndex(), queryContext.getQueryBuilder());
342 }
343
344
345
346
347
348
349
350
351
352
353
354 public String[] getLanguages(final HttpServletRequest request, final SearchRequestParams params) {
355 final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
356 if (params.getLanguages() != null) {
357 final Set<String> langSet = new HashSet<>();
358 for (final String lang : params.getLanguages()) {
359 if (StringUtil.isNotBlank(lang) && lang.length() < 1000) {
360 if (Constants.ALL_LANGUAGES.equalsIgnoreCase(lang)) {
361 langSet.add(Constants.ALL_LANGUAGES);
362 } else {
363 final String normalizeLang = systemHelper.normalizeLang(lang);
364 if (normalizeLang != null) {
365 langSet.add(normalizeLang);
366 }
367 }
368 }
369 }
370 if (langSet.size() > 1 && langSet.contains(Constants.ALL_LANGUAGES)) {
371 return new String[] { Constants.ALL_LANGUAGES };
372 }
373 langSet.remove(Constants.ALL_LANGUAGES);
374 return langSet.toArray(new String[langSet.size()]);
375 }
376 if (ComponentUtil.getFessConfig().isBrowserLocaleForSearchUsed()) {
377 final Set<String> langSet = new HashSet<>();
378 final Enumeration<Locale> locales = request.getLocales();
379 if (locales != null) {
380 while (locales.hasMoreElements()) {
381 final Locale locale = locales.nextElement();
382 final String normalizeLang = systemHelper.normalizeLang(locale.toString());
383 if (normalizeLang != null) {
384 langSet.add(normalizeLang);
385 }
386 }
387 if (!langSet.isEmpty()) {
388 return langSet.toArray(new String[langSet.size()]);
389 }
390 }
391 }
392 return StringUtil.EMPTY_STRINGS;
393 }
394
395
396
397
398
399
400
401
402
403 public OptionalEntity<Map<String, Object>> getDocumentByDocId(final String docId, final String[] fields,
404 final OptionalThing<FessUserBean> userBean) {
405 final FessConfig fessConfig = ComponentUtil.getFessConfig();
406 return ComponentUtil.getSearchEngineClient().getDocument(fessConfig.getIndexDocumentSearchIndex(), builder -> {
407 final BoolQueryBuilder boolQuery =
408 QueryBuilders.boolQuery().must(QueryBuilders.termQuery(fessConfig.getIndexFieldDocId(), docId));
409 final Set<String> roleSet = ComponentUtil.getRoleQueryHelper().build(SearchRequestType.JSON);
410 final QueryHelper queryHelper = ComponentUtil.getQueryHelper();
411 if (!roleSet.isEmpty()) {
412 queryHelper.buildRoleQuery(roleSet, boolQuery);
413 }
414 builder.setQuery(boolQuery);
415 builder.setFetchSource(fields, null);
416 queryHelper.processSearchPreference(builder, userBean, docId);
417 return true;
418 });
419
420 }
421
422
423
424
425
426
427
428
429
430
431 public List<Map<String, Object>> getDocumentListByDocIds(final String[] docIds, final String[] fields,
432 final OptionalThing<FessUserBean> userBean, final SearchRequestType searchRequestType) {
433 final FessConfig fessConfig = ComponentUtil.getFessConfig();
434 return ComponentUtil.getSearchEngineClient().getDocumentList(fessConfig.getIndexDocumentSearchIndex(), builder -> {
435 final BoolQueryBuilder boolQuery =
436 QueryBuilders.boolQuery().must(QueryBuilders.termsQuery(fessConfig.getIndexFieldDocId(), docIds));
437 final QueryHelper queryHelper = ComponentUtil.getQueryHelper();
438 if (searchRequestType != SearchRequestType.ADMIN_SEARCH) {
439 final Set<String> roleSet = ComponentUtil.getRoleQueryHelper().build(searchRequestType);
440 if (!roleSet.isEmpty()) {
441 queryHelper.buildRoleQuery(roleSet, boolQuery);
442 }
443 }
444 builder.setQuery(boolQuery);
445 builder.setSize(fessConfig.getPagingSearchPageMaxSizeAsInteger());
446 builder.setFetchSource(fields, null);
447 queryHelper.processSearchPreference(builder, userBean, String.join(StringUtil.EMPTY, docIds));
448 return true;
449 });
450 }
451
452
453
454
455
456
457
458
459
460 public boolean update(final String id, final String field, final Object value) {
461 return ComponentUtil.getSearchEngineClient().update(ComponentUtil.getFessConfig().getIndexDocumentUpdateIndex(), id, field, value);
462 }
463
464
465
466
467
468
469
470
471 public boolean update(final String id, final Consumer<UpdateRequestBuilder> builderLambda) {
472 try {
473 final FessConfig fessConfig = ComponentUtil.getFessConfig();
474 final UpdateRequestBuilder builder =
475 ComponentUtil.getSearchEngineClient().prepareUpdate().setIndex(fessConfig.getIndexDocumentUpdateIndex()).setId(id);
476 builderLambda.accept(builder);
477 final UpdateResponse response = builder.execute().actionGet(fessConfig.getIndexIndexTimeout());
478 return response.getResult() == Result.CREATED || response.getResult() == Result.UPDATED;
479 } catch (final OpenSearchException e) {
480 throw new SearchEngineClientException("Failed to update doc " + id, e);
481 }
482 }
483
484
485
486
487
488
489
490
491
492 public boolean bulkUpdate(final Consumer<BulkRequestBuilder> consumer) {
493 final BulkRequestBuilder builder = ComponentUtil.getSearchEngineClient().prepareBulk();
494 consumer.accept(builder);
495 try {
496 final BulkResponse response = builder.execute().get();
497 if (response.hasFailures()) {
498 throw new SearchEngineClientException(response.buildFailureMessage());
499 }
500 return true;
501 } catch (final InterruptedException e) {
502 throw new InterruptedRuntimeException(e);
503 } catch (final ExecutionException e) {
504 throw new SearchEngineClientException("Failed to update bulk data.", e);
505 }
506 }
507
508
509
510
511
512
513
514 protected SearchRequestParams rewrite(final SearchRequestParams params) {
515 SearchRequestParams newParams = params;
516 for (final SearchRequestParamsRewriter rewriter : searchRequestParamsRewriters) {
517 newParams = rewriter.rewrite(newParams);
518 }
519 return newParams;
520 }
521
522
523
524
525
526
527 public void addRewriter(final SearchRequestParamsRewriter rewriter) {
528 searchRequestParamsRewriters = Arrays.copyOf(searchRequestParamsRewriters, searchRequestParamsRewriters.length + 1);
529 searchRequestParamsRewriters[searchRequestParamsRewriters.length - 1] = rewriter;
530 }
531
532
533
534
535
536
537
538 public void storeSearchParameters() {
539 LaRequestUtil.getOptionalRequest().ifPresent(req -> {
540 final FessConfig fessConfig = ComponentUtil.getFessConfig();
541 final String requiredKeysStr = fessConfig.getCookieSearchParameterRequiredKeys();
542 if (StringUtil.isNotBlank(requiredKeysStr) && StreamUtil.split(requiredKeysStr, ",")
543 .get(stream -> stream.map(String::trim).filter(StringUtil::isNotEmpty).anyMatch(name -> {
544 final String[] values = req.getParameterValues(name);
545 if (values == null || values.length == 0 || StringUtil.isEmpty(values[0])) {
546 if (logger.isDebugEnabled()) {
547 logger.debug("Required parameter '{}' is missing or empty. Skip storing search parameters.", name);
548 }
549 return true;
550 }
551 return false;
552 }))) {
553 return;
554 }
555 final String keysStr = fessConfig.getCookieSearchParameterKeys();
556 if (StringUtil.isNotBlank(keysStr)) {
557 final RequestParameter[] parameters = StreamUtil.split(keysStr, ",").get(stream -> stream.map(String::trim).map(s -> {
558 if (StringUtil.isEmpty(s)) {
559 return null;
560 }
561 final String[] values = req.getParameterValues(s);
562 if (values == null || values.length == 0) {
563 if (logger.isDebugEnabled()) {
564 logger.debug("Parameter '{}' is not present or has no value.", s);
565 }
566 return null;
567 }
568 return new RequestParameter(s, values);
569 }).filter(o -> o != null).toArray(n -> new RequestParameter[n]));
570 if (parameters.length == 0) {
571 if (logger.isDebugEnabled()) {
572 logger.debug("No valid parameters found in request. Nothing to store.");
573 }
574 return;
575 }
576 try {
577 final String encoded = serializeParameters(parameters);
578 if (encoded.length() > fessConfig.getCookieSearchParameterMaxLengthAsInteger()) {
579 logger.warn("Encoded search parameters exceed the maximum cookie length: {} > {}. Skipping cookie storage.",
580 encoded.length(), fessConfig.getCookieSearchParameterMaxLengthAsInteger());
581 return;
582 }
583 LaResponseUtil.getOptionalResponse().ifPresent(res -> {
584 final Cookie cookie = new Cookie(fessConfig.getCookieSearchParameterName(), encoded);
585 cookie.setHttpOnly(Constants.TRUE.equalsIgnoreCase(fessConfig.getCookieSearchParameterHttpOnly()));
586 cookie.setSecure(isSearchParameterCookieSecure(req));
587 final String domain = fessConfig.getCookieSearchParameterDomain();
588 if (StringUtil.isNotBlank(domain)) {
589 cookie.setDomain(domain);
590 }
591 final String path = fessConfig.getCookieSearchParameterPath();
592 if (StringUtil.isNotBlank(path)) {
593 cookie.setPath(path);
594 }
595 cookie.setMaxAge(fessConfig.getCookieSearchParameterMaxAgeAsInteger());
596 cookie.setAttribute("SameSite", fessConfig.getCookieSearchParameterSameSite());
597 res.addCookie(cookie);
598 if (logger.isDebugEnabled()) {
599 logger.debug(
600 "Stored search parameters in cookie: name={}, size={}, maxAge={}, path={}, domain={}, secure={}, httpOnly={}, sameSite={}",
601 cookie.getName(), encoded.length(), cookie.getMaxAge(), cookie.getPath(), cookie.getDomain(),
602 cookie.getSecure(), cookie.isHttpOnly(), fessConfig.getCookieSearchParameterSameSite());
603 }
604 });
605 } catch (final Exception e) {
606 logger.warn("Failed to store search parameters in cookie.", e);
607 }
608 }
609 });
610 }
611
612
613
614
615
616
617
618
619 protected String serializeParameters(final RequestParameter[] parameters) {
620 final List<Object[]> compactList = new ArrayList<>();
621 for (final RequestParameter p : parameters) {
622 compactList.add(new Object[] { p.getName(), p.getValues() });
623 }
624 try {
625 final String json = mapper.writeValueAsString(compactList);
626 final byte[] compressed = gzipCompress(json.getBytes(StandardCharsets.UTF_8));
627 return Base64.getUrlEncoder().withoutPadding().encodeToString(compressed);
628 } catch (final Exception e) {
629 throw new SearchQueryException("Failed to serialize a query: " + Arrays.toString(parameters), e);
630 }
631 }
632
633
634
635
636
637
638
639
640 protected byte[] gzipCompress(final byte[] data) {
641 try (final ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
642 try (final GZIPOutputStream gzipOut = new GZIPOutputStream(bos)) {
643 gzipOut.write(data);
644 }
645 return bos.toByteArray();
646 } catch (final IOException e) {
647 throw new IORuntimeException(e);
648 }
649 }
650
651
652
653
654
655
656 public RequestParameter[] getSearchParameters() {
657 return LaRequestUtil.getOptionalRequest().map(req -> {
658 final FessConfig fessConfig = ComponentUtil.getFessConfig();
659 final String cookieName = fessConfig.getCookieSearchParameterName();
660 final Cookie[] cookies = req.getCookies();
661 if (cookies != null) {
662 for (final Cookie cookie : cookies) {
663 if (cookieName.equals(cookie.getName())) {
664 try {
665 final String encoded = cookie.getValue();
666 final byte[] compressed = Base64.getUrlDecoder().decode(encoded);
667 final byte[] jsonBytes = gzipDecompress(compressed);
668 final List<?> list = mapper.readValue(jsonBytes, List.class);
669
670 final List<RequestParameter> result = new ArrayList<>();
671 for (Object item : list) {
672 if (item instanceof List<?> pair) {
673 if (pair.size() == 2 && pair.get(0) instanceof String name
674 && pair.get(1) instanceof List<?> valueList) {
675 final String[] values =
676 valueList.stream().filter(v -> v instanceof String).toArray(n -> new String[n]);
677 result.add(new RequestParameter(name, values));
678 }
679 }
680 }
681 LaResponseUtil.getOptionalResponse().ifPresent(res -> {
682 final Cookie invalidCookie = new Cookie(fessConfig.getCookieSearchParameterName(), StringUtil.EMPTY);
683 invalidCookie.setHttpOnly(Constants.TRUE.equalsIgnoreCase(fessConfig.getCookieSearchParameterHttpOnly()));
684 invalidCookie.setSecure(isSearchParameterCookieSecure(req));
685 invalidCookie.setPath(fessConfig.getCookieSearchParameterPath());
686 invalidCookie.setMaxAge(0);
687 res.addCookie(invalidCookie);
688 });
689 return result.toArray(n -> new RequestParameter[n]);
690 } catch (final Exception e) {
691 logger.warn("Failed to deserialize search parameters from cookie.", e);
692 return new RequestParameter[0];
693 }
694 }
695 }
696 }
697 return new RequestParameter[0];
698 }).orElse(new RequestParameter[0]);
699 }
700
701
702
703
704
705
706
707
708 protected byte[] gzipDecompress(final byte[] compressed) {
709 try (final ByteArrayInputStream bis = new ByteArrayInputStream(compressed);
710 final GZIPInputStream gzipIn = new GZIPInputStream(bis);
711 final ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
712 final byte[] buffer = new byte[1024];
713 int len;
714 while ((len = gzipIn.read(buffer)) > 0) {
715 bos.write(buffer, 0, len);
716 }
717 return bos.toByteArray();
718 } catch (final IOException e) {
719 throw new IORuntimeException(e);
720 }
721 }
722
723
724
725
726
727
728
729 protected boolean isSearchParameterCookieSecure(final HttpServletRequest request) {
730 final FessConfig fessConfig = ComponentUtil.getFessConfig();
731 final String secure = fessConfig.getCookieSearchParameterSecure();
732 if (StringUtil.isBlank(secure)) {
733 final String forwardedProto = request.getHeader("X-Forwarded-Proto");
734 if ("https".equalsIgnoreCase(forwardedProto)) {
735 return true;
736 }
737 return request.isSecure();
738 }
739 return Constants.TRUE.equalsIgnoreCase(secure);
740 }
741
742
743
744
745
746
747
748 public interface SearchRequestParamsRewriter {
749
750
751
752
753
754
755 SearchRequestParams rewrite(SearchRequestParams params);
756 }
757 }