1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.util;
17
18 import static org.codelibs.core.stream.StreamUtil.split;
19
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.regex.Pattern;
27 import java.util.stream.Collectors;
28
29 import javax.xml.XMLConstants;
30 import javax.xml.parsers.SAXParser;
31 import javax.xml.parsers.SAXParserFactory;
32
33 import org.apache.logging.log4j.LogManager;
34 import org.apache.logging.log4j.Logger;
35 import org.codelibs.core.lang.StringUtil;
36 import org.codelibs.fess.Constants;
37 import org.codelibs.fess.exception.GsaConfigException;
38 import org.codelibs.fess.opensearch.config.exentity.FileConfig;
39 import org.codelibs.fess.opensearch.config.exentity.LabelType;
40 import org.codelibs.fess.opensearch.config.exentity.WebConfig;
41 import org.dbflute.optional.OptionalEntity;
42 import org.xml.sax.Attributes;
43 import org.xml.sax.InputSource;
44 import org.xml.sax.SAXException;
45 import org.xml.sax.helpers.DefaultHandler;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 public class GsaConfigParser extends DefaultHandler {
63
64
65 private static final Logger logger = LogManager.getLogger(GsaConfigParser.class);
66
67
68 public static final String REGEXP = "regexp:";
69
70
71 public static final String REGEXP_CASE = "regexpCase:";
72
73
74 public static final String REGEXP_IGNORE_CASE = "regexpIgnoreCase:";
75
76
77 public static final String CONTAINS = "contains:";
78
79
80 protected static final String COLLECTIONS = "collections";
81
82
83 protected static final String COLLECTION = "collection";
84
85
86 protected static final String GLOBALPARAMS = "globalparams";
87
88
89 protected static final String START_URLS = "start_urls";
90
91
92 protected static final String GOOD_URLS = "good_urls";
93
94
95 protected static final String BAD_URLS = "bad_urls";
96
97
98 protected String[] webProtocols = { "http:", "https:" };
99
100
101 protected String[] fileProtocols = { "file:", "smb:", "smb1:", "ftp:", "storage:" };
102
103
104 protected LinkedList<String> tagQueue;
105
106
107 protected List<LabelType> labelList;
108
109
110 protected LabelType labelType;
111
112
113 protected Map<String, String> globalParams = new HashMap<>();
114
115
116 protected WebConfig webConfig = null;
117
118
119 protected FileConfig fileConfig = null;
120
121
122 protected StringBuilder textBuf = new StringBuilder(1000);
123
124
125 protected String userAgent = "gsa-crawler";
126
127
128
129
130 public GsaConfigParser() {
131 super();
132 }
133
134
135
136
137
138
139
140
141
142 public void parse(final InputSource is) {
143 try {
144 final SAXParserFactory factory = SAXParserFactory.newInstance();
145 factory.setFeature(org.codelibs.fess.crawler.Constants.FEATURE_SECURE_PROCESSING, true);
146 factory.setFeature(org.codelibs.fess.crawler.Constants.FEATURE_EXTERNAL_GENERAL_ENTITIES, false);
147 factory.setFeature(org.codelibs.fess.crawler.Constants.FEATURE_EXTERNAL_PARAMETER_ENTITIES, false);
148 final SAXParser parser = factory.newSAXParser();
149 parser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, StringUtil.EMPTY);
150 parser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, StringUtil.EMPTY);
151 parser.parse(is, this);
152 } catch (final Exception e) {
153 throw new GsaConfigException("Failed to parse XML file.", e);
154 }
155 }
156
157
158
159
160
161
162
163 @Override
164 public void startDocument() throws SAXException {
165 tagQueue = new LinkedList<>();
166 labelList = new ArrayList<>();
167 labelType = null;
168 }
169
170
171
172
173
174
175
176 @Override
177 public void endDocument() throws SAXException {
178 globalParams.clear();
179 tagQueue.clear();
180 }
181
182
183
184
185
186
187
188
189
190
191
192 @Override
193 public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
194 throws SAXException {
195 if (logger.isDebugEnabled()) {
196 logger.debug("Start element: name={}", qName);
197 }
198 if (tagQueue.isEmpty() && !"eef".equalsIgnoreCase(qName)) {
199 throw new GsaConfigException("Invalid GSA configuration format. Root element must be 'eef', but found: " + qName);
200 }
201 if (COLLECTION.equalsIgnoreCase(qName) && COLLECTIONS.equalsIgnoreCase(tagQueue.peekLast())) {
202 final long now = ComponentUtil.getSystemHelper().getCurrentTimeAsLong();
203 final String name = attributes.getValue("Name");
204 labelType = new LabelType();
205 labelType.setName(name);
206 labelType.setValue(name);
207 labelType.setPermissions(new String[] { "Rguest" });
208 labelType.setCreatedBy(Constants.SYSTEM_USER);
209 labelType.setCreatedTime(now);
210 labelType.setUpdatedBy(Constants.SYSTEM_USER);
211 labelType.setUpdatedTime(now);
212 }
213 tagQueue.offer(qName);
214 }
215
216
217
218
219
220
221
222
223
224
225
226 @Override
227 public void endElement(final String uri, final String localName, final String qName) throws SAXException {
228 if (logger.isDebugEnabled()) {
229 logger.debug("End element: name={}", qName);
230 }
231 if (GOOD_URLS.equalsIgnoreCase(qName)) {
232 if (labelType != null) {
233 labelType.setIncludedPaths(parseFilterPaths(textBuf.toString(), true, true));
234 } else if (GLOBALPARAMS.equalsIgnoreCase(tagQueue.get(tagQueue.size() - 2))) {
235 globalParams.put(GOOD_URLS, textBuf.toString());
236 }
237 } else if (BAD_URLS.equalsIgnoreCase(qName)) {
238 if (labelType != null) {
239 labelType.setExcludedPaths(parseFilterPaths(textBuf.toString(), true, true));
240 } else if (GLOBALPARAMS.equalsIgnoreCase(tagQueue.get(tagQueue.size() - 2))) {
241 globalParams.put(BAD_URLS, textBuf.toString());
242 }
243 } else if (START_URLS.equalsIgnoreCase(qName) && GLOBALPARAMS.equalsIgnoreCase(tagQueue.get(tagQueue.size() - 2))) {
244 globalParams.put(START_URLS, textBuf.toString());
245 } else if (labelType != null && COLLECTION.equalsIgnoreCase(qName)) {
246 labelList.add(labelType);
247 labelType = null;
248 } else if (GLOBALPARAMS.equalsIgnoreCase(qName)) {
249 final Object startUrls = globalParams.get(START_URLS);
250 if (startUrls != null) {
251 final long now = ComponentUtil.getSystemHelper().getCurrentTimeAsLong();
252 final List<String> urlList = split(startUrls.toString(), "\n")
253 .get(stream -> stream.map(String::trim).filter(StringUtil::isNotBlank).collect(Collectors.toList()));
254
255 final String webUrls = urlList.stream()
256 .filter(s -> Arrays.stream(webProtocols).anyMatch(p -> s.startsWith(p)))
257 .collect(Collectors.joining("\n"));
258 if (StringUtil.isNotBlank(webUrls)) {
259 webConfig = new WebConfig();
260 webConfig.setName("Default");
261 webConfig.setAvailable(true);
262 webConfig.setBoost(1.0f);
263 webConfig.setConfigParameter(StringUtil.EMPTY);
264 webConfig.setIntervalTime(1000);
265 webConfig.setNumOfThread(3);
266 webConfig.setSortOrder(1);
267 webConfig.setUrls(webUrls);
268 webConfig.setIncludedUrls(parseFilterPaths(globalParams.get(GOOD_URLS), true, false));
269 webConfig.setIncludedDocUrls(StringUtil.EMPTY);
270 webConfig.setExcludedUrls(parseFilterPaths(globalParams.get(BAD_URLS), true, false));
271 webConfig.setExcludedDocUrls(StringUtil.EMPTY);
272 webConfig.setUserAgent(userAgent);
273 webConfig.setPermissions(new String[] { "Rguest" });
274 webConfig.setCreatedBy(Constants.SYSTEM_USER);
275 webConfig.setCreatedTime(now);
276 webConfig.setUpdatedBy(Constants.SYSTEM_USER);
277 webConfig.setUpdatedTime(now);
278 }
279
280 final String fileUrls = urlList.stream()
281 .filter(s -> Arrays.stream(fileProtocols).anyMatch(p -> s.startsWith(p)))
282 .collect(Collectors.joining("\n"));
283 if (StringUtil.isNotBlank(fileUrls)) {
284 fileConfig = new FileConfig();
285 fileConfig.setName("Default");
286 fileConfig.setAvailable(true);
287 fileConfig.setBoost(1.0f);
288 fileConfig.setConfigParameter(StringUtil.EMPTY);
289 fileConfig.setIntervalTime(0);
290 fileConfig.setNumOfThread(5);
291 fileConfig.setSortOrder(2);
292 fileConfig.setPaths(fileUrls);
293 fileConfig.setIncludedPaths(parseFilterPaths(globalParams.get(GOOD_URLS), false, true));
294 fileConfig.setIncludedDocPaths(StringUtil.EMPTY);
295 fileConfig.setExcludedPaths(parseFilterPaths(globalParams.get(BAD_URLS), false, true));
296 fileConfig.setExcludedDocPaths(StringUtil.EMPTY);
297 fileConfig.setPermissions(new String[] { "Rguest" });
298 fileConfig.setCreatedBy(Constants.SYSTEM_USER);
299 fileConfig.setCreatedTime(now);
300 fileConfig.setUpdatedBy(Constants.SYSTEM_USER);
301 fileConfig.setUpdatedTime(now);
302 }
303 }
304 } else if ("user_agent".equalsIgnoreCase(qName) && GLOBALPARAMS.equalsIgnoreCase(tagQueue.get(tagQueue.size() - 2))) {
305 userAgent = textBuf.toString().trim();
306 }
307 tagQueue.pollLast();
308 textBuf.setLength(0);
309 }
310
311
312
313
314
315
316
317
318
319
320 @Override
321 public void characters(final char[] ch, final int start, final int length) throws SAXException {
322 final String text = new String(ch, start, length);
323 if (logger.isDebugEnabled()) {
324 logger.debug("Text: content={}", text);
325 }
326 textBuf.append(text);
327 }
328
329
330
331
332
333
334
335
336
337
338 protected String parseFilterPaths(final String text, final boolean web, final boolean file) {
339 return split(text, "\n")
340 .get(stream -> stream.map(String::trim).filter(StringUtil::isNotBlank).map(this::getFilterPath).filter(s -> {
341 if (StringUtil.isBlank(s)) {
342 return false;
343 }
344 if (Arrays.stream(webProtocols).anyMatch(p -> s.startsWith(p))) {
345 return web;
346 }
347 if (Arrays.stream(fileProtocols).anyMatch(p -> s.startsWith(p))) {
348 return file;
349 }
350 return true;
351 }).collect(Collectors.joining("\n")));
352 }
353
354
355
356
357
358
359
360
361 protected String getFilterPath(final String s) {
362 if (s.startsWith("#")) {
363 return StringUtil.EMPTY;
364 }
365 if (s.startsWith(CONTAINS)) {
366 final String v = s.substring(CONTAINS.length());
367 final StringBuilder buf = new StringBuilder(100);
368 return ".*" + appendFileterPath(buf, escape(v)) + ".*";
369 }
370 if (s.startsWith(REGEXP_IGNORE_CASE)) {
371 final String v = s.substring(REGEXP_IGNORE_CASE.length());
372 final StringBuilder buf = new StringBuilder(100);
373 buf.append("(?i)");
374 return appendFileterPath(buf, unescape(v));
375 }
376 if (s.startsWith(REGEXP_CASE)) {
377 final String v = s.substring(REGEXP_CASE.length());
378 final StringBuilder buf = new StringBuilder(100);
379 return appendFileterPath(buf, unescape(v));
380 }
381 if (s.startsWith(REGEXP)) {
382 final String v = s.substring(REGEXP.length());
383 final StringBuilder buf = new StringBuilder(100);
384 return appendFileterPath(buf, unescape(v));
385 }
386 if (Arrays.stream(webProtocols).anyMatch(p -> s.startsWith(p)) || Arrays.stream(fileProtocols).anyMatch(p -> s.startsWith(p))) {
387 return escape(s) + ".*";
388 }
389 final StringBuilder buf = new StringBuilder(100);
390 return appendFileterPath(buf, escape(s));
391 }
392
393
394
395
396
397
398
399
400 protected String escape(final String s) {
401 if (s.startsWith("#")) {
402 return StringUtil.EMPTY;
403 }
404 if (s.startsWith("^") && s.endsWith("$")) {
405 return "^" + Pattern.quote(s.substring(1, s.length() - 1)) + "$";
406 }
407 if (s.startsWith("^")) {
408 return "^" + Pattern.quote(s.substring(1));
409 }
410 if (s.endsWith("$")) {
411 return Pattern.quote(s.substring(0, s.length() - 1)) + "$";
412 }
413 return Pattern.quote(s);
414 }
415
416
417
418
419
420
421
422
423 protected String unescape(final String s) {
424 return s.replace("\\\\", "\\");
425 }
426
427
428
429
430
431
432
433
434
435 protected String appendFileterPath(final StringBuilder buf, final String v) {
436 if (StringUtil.isBlank(v)) {
437 return StringUtil.EMPTY;
438 }
439
440 if (v.startsWith("^")) {
441 buf.append(v);
442 if (!v.endsWith("$")) {
443 buf.append(".*");
444 }
445 } else if (v.endsWith("$")) {
446 buf.append(".*");
447 buf.append(v);
448 } else if (v.endsWith("/\\E")) {
449 buf.append(".*");
450 buf.append(v);
451 buf.append(".*");
452 } else {
453 buf.append(v);
454 }
455 return buf.toString();
456 }
457
458
459
460
461
462
463 public void setWebProtocols(final String[] webProtocols) {
464 this.webProtocols = webProtocols;
465 }
466
467
468
469
470
471
472 public void setFileProtocols(final String[] fileProtocols) {
473 this.fileProtocols = fileProtocols;
474 }
475
476
477
478
479
480
481
482 @Override
483 public String toString() {
484 return "GsaConfigParser [labelList=" + labelList + ", webConfig=" + webConfig + ", fileConfig=" + fileConfig + "]";
485 }
486
487
488
489
490
491
492 public OptionalEntity<WebConfig> getWebConfig() {
493 return OptionalUtil.ofNullable(webConfig);
494 }
495
496
497
498
499
500
501 public OptionalEntity<FileConfig> getFileConfig() {
502 return OptionalUtil.ofNullable(fileConfig);
503 }
504
505
506
507
508
509
510
511 public LabelType[] getLabelTypes() {
512 return labelList.toArray(new LabelType[labelList.size()]);
513 }
514
515 }