001
014
015 package com.liferay.portal.servlet.filters.strip;
016
017 import com.liferay.portal.kernel.cache.key.CacheKeyGenerator;
018 import com.liferay.portal.kernel.cache.key.CacheKeyGeneratorUtil;
019 import com.liferay.portal.kernel.concurrent.ConcurrentLFUCache;
020 import com.liferay.portal.kernel.io.OutputStreamWriter;
021 import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
022 import com.liferay.portal.kernel.log.Log;
023 import com.liferay.portal.kernel.log.LogFactoryUtil;
024 import com.liferay.portal.kernel.portlet.LiferayWindowState;
025 import com.liferay.portal.kernel.scripting.ScriptingException;
026 import com.liferay.portal.kernel.servlet.HttpHeaders;
027 import com.liferay.portal.kernel.servlet.ServletResponseUtil;
028 import com.liferay.portal.kernel.servlet.StringServletResponse;
029 import com.liferay.portal.kernel.util.CharPool;
030 import com.liferay.portal.kernel.util.ContentTypes;
031 import com.liferay.portal.kernel.util.GetterUtil;
032 import com.liferay.portal.kernel.util.HttpUtil;
033 import com.liferay.portal.kernel.util.JavaConstants;
034 import com.liferay.portal.kernel.util.KMPSearch;
035 import com.liferay.portal.kernel.util.ParamUtil;
036 import com.liferay.portal.kernel.util.Validator;
037 import com.liferay.portal.servlet.filters.BasePortalFilter;
038 import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
039 import com.liferay.portal.util.MinifierUtil;
040 import com.liferay.portal.util.PropsValues;
041
042 import java.io.Writer;
043
044 import java.nio.CharBuffer;
045
046 import java.util.HashSet;
047 import java.util.Set;
048 import java.util.regex.Matcher;
049 import java.util.regex.Pattern;
050
051 import javax.servlet.FilterChain;
052 import javax.servlet.FilterConfig;
053 import javax.servlet.http.HttpServletRequest;
054 import javax.servlet.http.HttpServletResponse;
055
056
061 public class StripFilter extends BasePortalFilter {
062
063 public static final String SKIP_FILTER =
064 StripFilter.class.getName() + "SKIP_FILTER";
065
066 public StripFilter() {
067 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
068 _minifierCache = new ConcurrentLFUCache<String, String>(
069 PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE);
070 }
071 }
072
073 @Override
074 public void init(FilterConfig filterConfig) {
075 super.init(filterConfig);
076
077 for (String ignorePath : PropsValues.STRIP_IGNORE_PATHS) {
078 _ignorePaths.add(ignorePath);
079 }
080 }
081
082 @Override
083 public boolean isFilterEnabled(
084 HttpServletRequest request, HttpServletResponse response) {
085
086 if (isStrip(request) && !isInclude(request) &&
087 !isAlreadyFiltered(request)) {
088
089 return true;
090 }
091 else {
092 return false;
093 }
094 }
095
096 protected String extractContent(CharBuffer charBuffer, int length) {
097
098
099
100
105
106 CharBuffer duplicateCharBuffer = charBuffer.duplicate();
107
108 int position = duplicateCharBuffer.position() + length;
109
110 String content = duplicateCharBuffer.limit(position).toString();
111
112 charBuffer.position(position);
113
114 return content;
115 }
116
117 protected boolean hasLanguageAttribute(
118 CharBuffer charBuffer, int startPos, int length) {
119
120 if (!PropsValues.STRIP_JS_LANGUAGE_ATTRIBUTE_SUPPORT_ENABLED) {
121 return false;
122 }
123
124 if (KMPSearch.search(
125 charBuffer, startPos, length, _MARKER_LANGUAGE,
126 _MARKER_LANGUAGE_NEXTS) == -1) {
127
128 return false;
129 }
130
131 Matcher matcher = _javaScriptPattern.matcher(charBuffer);
132
133 if (matcher.find()) {
134 return true;
135 }
136
137 return false;
138 }
139
140 protected boolean hasMarker(CharBuffer charBuffer, char[] marker) {
141 int position = charBuffer.position();
142
143 if ((position + marker.length) >= charBuffer.limit()) {
144 return false;
145 }
146
147 for (int i = 0; i < marker.length; i++) {
148 char c = marker[i];
149
150 char oldC = charBuffer.charAt(i);
151
152 if ((c != oldC) && (Character.toUpperCase(c) != oldC)) {
153 return false;
154 }
155 }
156
157 return true;
158 }
159
160 protected boolean isAlreadyFiltered(HttpServletRequest request) {
161 if (request.getAttribute(SKIP_FILTER) != null) {
162 return true;
163 }
164 else {
165 return false;
166 }
167 }
168
169 protected boolean isInclude(HttpServletRequest request) {
170 String uri = (String)request.getAttribute(
171 JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
172
173 if (uri == null) {
174 return false;
175 }
176 else {
177 return true;
178 }
179 }
180
181 protected boolean isStrip(HttpServletRequest request) {
182 if (!ParamUtil.getBoolean(request, _STRIP, true)) {
183 return false;
184 }
185
186 String path = request.getPathInfo();
187
188 if (_ignorePaths.contains(path)) {
189 if (_log.isDebugEnabled()) {
190 _log.debug("Ignore path " + path);
191 }
192
193 return false;
194 }
195
196
197
198
199
200 String lifecycle = ParamUtil.getString(request, "p_p_lifecycle");
201
202 if ((lifecycle.equals("1") &&
203 LiferayWindowState.isExclusive(request)) ||
204 lifecycle.equals("2")) {
205
206 return false;
207 }
208 else {
209 return true;
210 }
211 }
212
213 protected void outputCloseTag(
214 CharBuffer charBuffer, Writer writer, String closeTag)
215 throws Exception {
216
217 writer.write(closeTag);
218
219 charBuffer.position(charBuffer.position() + closeTag.length());
220
221 skipWhiteSpace(charBuffer, writer, true);
222 }
223
224 protected void outputOpenTag(
225 CharBuffer charBuffer, Writer writer, char[] openTag)
226 throws Exception {
227
228 writer.write(openTag);
229
230 charBuffer.position(charBuffer.position() + openTag.length);
231 }
232
233 protected void processCSS(
234 HttpServletRequest request, HttpServletResponse response,
235 CharBuffer charBuffer, Writer writer)
236 throws Exception {
237
238 outputOpenTag(charBuffer, writer, _MARKER_STYLE_OPEN);
239
240 int length = KMPSearch.search(
241 charBuffer, _MARKER_STYLE_CLOSE, _MARKER_STYLE_CLOSE_NEXTS);
242
243 if (length == -1) {
244 if (_log.isWarnEnabled()) {
245 _log.warn("Missing </style>");
246 }
247
248 return;
249 }
250
251 if (length == 0) {
252 outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
253
254 return;
255 }
256
257 String content = extractContent(charBuffer, length);
258
259 String minifiedContent = content;
260
261 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
262 CacheKeyGenerator cacheKeyGenerator =
263 CacheKeyGeneratorUtil.getCacheKeyGenerator(
264 StripFilter.class.getName());
265
266 String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
267
268 minifiedContent = _minifierCache.get(key);
269
270 if (minifiedContent == null) {
271 if (PropsValues.STRIP_CSS_SASS_ENABLED) {
272 try {
273 content = DynamicCSSUtil.parseSass(
274 request, key, content);
275 }
276 catch (ScriptingException se) {
277 _log.error("Unable to parse SASS on CSS " + key, se);
278
279 if (_log.isDebugEnabled()) {
280 _log.debug(content);
281 }
282
283 if (response != null) {
284 response.setHeader(
285 HttpHeaders.CACHE_CONTROL,
286 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
287 }
288 }
289 }
290
291 minifiedContent = MinifierUtil.minifyCss(content);
292
293 boolean skipCache = false;
294
295 for (String skipCss :
296 PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SKIP_CSS) {
297
298 if (minifiedContent.contains(skipCss)) {
299 skipCache = true;
300
301 break;
302 }
303 }
304
305 if (!skipCache) {
306 _minifierCache.put(key, minifiedContent);
307 }
308 }
309 }
310
311 if (!Validator.isNull(minifiedContent)) {
312 writer.write(minifiedContent);
313 }
314
315 outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
316 }
317
318 @Override
319 protected void processFilter(
320 HttpServletRequest request, HttpServletResponse response,
321 FilterChain filterChain)
322 throws Exception {
323
324 if (_log.isDebugEnabled()) {
325 String completeURL = HttpUtil.getCompleteURL(request);
326
327 _log.debug("Stripping " + completeURL);
328 }
329
330 request.setAttribute(SKIP_FILTER, Boolean.TRUE);
331
332 StringServletResponse stringResponse = new StringServletResponse(
333 response);
334
335 processFilter(StripFilter.class, request, stringResponse, filterChain);
336
337 String contentType = GetterUtil.getString(
338 stringResponse.getContentType()).toLowerCase();
339
340 if (_log.isDebugEnabled()) {
341 _log.debug("Stripping content of type " + contentType);
342 }
343
344 response.setContentType(contentType);
345
346 if (contentType.startsWith(ContentTypes.TEXT_HTML) &&
347 (stringResponse.getStatus() == HttpServletResponse.SC_OK)) {
348
349 CharBuffer oldCharBuffer = CharBuffer.wrap(
350 stringResponse.getString());
351
352 boolean ensureContentLength = ParamUtil.getBoolean(
353 request, _ENSURE_CONTENT_LENGTH);
354
355 if (ensureContentLength) {
356 UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
357 new UnsyncByteArrayOutputStream();
358
359 strip(
360 request, response, oldCharBuffer,
361 new OutputStreamWriter(unsyncByteArrayOutputStream));
362
363 response.setContentLength(unsyncByteArrayOutputStream.size());
364
365 unsyncByteArrayOutputStream.writeTo(response.getOutputStream());
366 }
367 else {
368 strip(request, response, oldCharBuffer, response.getWriter());
369 }
370 }
371 else {
372 ServletResponseUtil.write(response, stringResponse);
373 }
374 }
375
376 protected void processInput(CharBuffer oldCharBuffer, Writer writer)
377 throws Exception {
378
379 int length = KMPSearch.search(
380 oldCharBuffer, _MARKER_INPUT_OPEN.length + 1, _MARKER_INPUT_CLOSE,
381 _MARKER_INPUT_CLOSE_NEXTS);
382
383 if (length == -1) {
384 if (_log.isWarnEnabled()) {
385 _log.warn("Missing />");
386 }
387
388 outputOpenTag(oldCharBuffer, writer, _MARKER_INPUT_OPEN);
389
390 return;
391 }
392
393 length += _MARKER_INPUT_CLOSE.length();
394
395 String content = extractContent(oldCharBuffer, length);
396
397 writer.write(content);
398
399 skipWhiteSpace(oldCharBuffer, writer, true);
400 }
401
402 protected void processJavaScript(
403 CharBuffer charBuffer, Writer writer, char[] openTag)
404 throws Exception {
405
406 int endPos = openTag.length + 1;
407
408 char c = charBuffer.charAt(openTag.length);
409
410 if (c == CharPool.SPACE) {
411 int startPos = openTag.length + 1;
412
413 for (int i = startPos; i < charBuffer.length(); i++) {
414 c = charBuffer.charAt(i);
415
416 if (c == CharPool.GREATER_THAN) {
417
418
419
420 endPos = i + 1;
421
422 int length = i - startPos;
423
424 if ((length < _MARKER_TYPE_JAVASCRIPT.length()) ||
425 (KMPSearch.search(
426 charBuffer, startPos, length,
427 _MARKER_TYPE_JAVASCRIPT,
428 _MARKER_TYPE_JAVASCRIPT_NEXTS) == -1)) {
429
430
431
432
433
434
435
436 if (!hasLanguageAttribute(
437 charBuffer, startPos, length)) {
438
439 return;
440 }
441 }
442
443
444
445
446 break;
447 }
448 else if (c == CharPool.LESS_THAN) {
449
450
451
452 return;
453 }
454 }
455
456 if (endPos == charBuffer.length()) {
457
458
459
460 return;
461 }
462 }
463 else if (c != CharPool.GREATER_THAN) {
464
465
466
467 return;
468 }
469
470 writer.append(charBuffer, 0, endPos);
471
472 charBuffer.position(charBuffer.position() + endPos);
473
474 int length = KMPSearch.search(
475 charBuffer, _MARKER_SCRIPT_CLOSE, _MARKER_SCRIPT_CLOSE_NEXTS);
476
477 if (length == -1) {
478 if (_log.isWarnEnabled()) {
479 _log.warn("Missing </script>");
480 }
481
482 return;
483 }
484
485 if (length == 0) {
486 outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
487
488 return;
489 }
490
491 String content = extractContent(charBuffer, length);
492
493 String minifiedContent = content;
494
495 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
496 CacheKeyGenerator cacheKeyGenerator =
497 CacheKeyGeneratorUtil.getCacheKeyGenerator(
498 StripFilter.class.getName());
499
500 String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
501
502 minifiedContent = _minifierCache.get(key);
503
504 if (minifiedContent == null) {
505 minifiedContent = MinifierUtil.minifyJavaScript(content);
506
507 boolean skipCache = false;
508
509 for (String skipJavaScript :
510 PropsValues.
511 MINIFIER_INLINE_CONTENT_CACHE_SKIP_JAVASCRIPT) {
512
513 if (minifiedContent.contains(skipJavaScript)) {
514 skipCache = true;
515
516 break;
517 }
518 }
519
520 if (!skipCache) {
521 _minifierCache.put(key, minifiedContent);
522 }
523 }
524 }
525
526 if (!Validator.isNull(minifiedContent)) {
527 writer.write(_CDATA_OPEN);
528 writer.write(minifiedContent);
529 writer.write(_CDATA_CLOSE);
530 }
531
532 outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
533 }
534
535 protected void processPre(CharBuffer oldCharBuffer, Writer writer)
536 throws Exception {
537
538 int length = KMPSearch.search(
539 oldCharBuffer, _MARKER_PRE_OPEN.length + 1, _MARKER_PRE_CLOSE,
540 _MARKER_PRE_CLOSE_NEXTS);
541
542 if (length == -1) {
543 if (_log.isWarnEnabled()) {
544 _log.warn("Missing </pre>");
545 }
546
547 outputOpenTag(oldCharBuffer, writer, _MARKER_PRE_OPEN);
548
549 return;
550 }
551
552 length += _MARKER_PRE_CLOSE.length();
553
554 String content = extractContent(oldCharBuffer, length);
555
556 writer.write(content);
557
558 skipWhiteSpace(oldCharBuffer, writer, true);
559 }
560
561 protected void processTextArea(CharBuffer oldCharBuffer, Writer writer)
562 throws Exception {
563
564 int length = KMPSearch.search(
565 oldCharBuffer, _MARKER_TEXTAREA_OPEN.length + 1,
566 _MARKER_TEXTAREA_CLOSE, _MARKER_TEXTAREA_CLOSE_NEXTS);
567
568 if (length == -1) {
569 if (_log.isWarnEnabled()) {
570 _log.warn("Missing </textArea>");
571 }
572
573 outputOpenTag(oldCharBuffer, writer, _MARKER_TEXTAREA_OPEN);
574 return;
575 }
576
577 length += _MARKER_TEXTAREA_CLOSE.length();
578
579 String content = extractContent(oldCharBuffer, length);
580
581 writer.write(content);
582
583 skipWhiteSpace(oldCharBuffer, writer, true);
584 }
585
586 protected boolean skipWhiteSpace(
587 CharBuffer charBuffer, Writer writer, boolean appendSeparator)
588 throws Exception {
589
590 boolean skipped = false;
591
592 for (int i = charBuffer.position(); i < charBuffer.limit(); i++) {
593 char c = charBuffer.get();
594
595 if ((c == CharPool.SPACE) || (c == CharPool.TAB) ||
596 (c == CharPool.RETURN) || (c == CharPool.NEW_LINE)) {
597
598 skipped = true;
599
600 continue;
601 }
602 else {
603 charBuffer.position(i);
604
605 break;
606 }
607 }
608
609 if (skipped && appendSeparator) {
610 writer.write(CharPool.SPACE);
611 }
612
613 return skipped;
614 }
615
616 protected void strip(
617 HttpServletRequest request, HttpServletResponse response,
618 CharBuffer charBuffer, Writer writer)
619 throws Exception {
620
621 skipWhiteSpace(charBuffer, writer, false);
622
623 while (charBuffer.hasRemaining()) {
624 char c = charBuffer.get();
625
626 writer.write(c);
627
628 if (c == CharPool.LESS_THAN) {
629 if (hasMarker(charBuffer, _MARKER_INPUT_OPEN)) {
630 processInput(charBuffer, writer);
631
632 continue;
633 }
634 else if (hasMarker(charBuffer, _MARKER_PRE_OPEN)) {
635 processPre(charBuffer, writer);
636
637 continue;
638 }
639 else if (hasMarker(charBuffer, _MARKER_TEXTAREA_OPEN)) {
640 processTextArea(charBuffer, writer);
641
642 continue;
643 }
644 else if (hasMarker(charBuffer, _MARKER_SCRIPT_OPEN)) {
645 processJavaScript(charBuffer, writer, _MARKER_SCRIPT_OPEN);
646
647 continue;
648 }
649 else if (hasMarker(charBuffer, _MARKER_STYLE_OPEN)) {
650 processCSS(request, response, charBuffer, writer);
651
652 continue;
653 }
654 }
655 else if (c == CharPool.GREATER_THAN) {
656 skipWhiteSpace(charBuffer, writer, true);
657 }
658
659 skipWhiteSpace(charBuffer, writer, true);
660 }
661
662 writer.flush();
663 }
664
665 private static final String _CDATA_CLOSE = "";
666
667 private static final String _CDATA_OPEN = "";
668
669 private static final String _ENSURE_CONTENT_LENGTH = "ensureContentLength";
670
671 private static final String _MARKER_INPUT_CLOSE = "/>";
672
673 private static final int[] _MARKER_INPUT_CLOSE_NEXTS =
674 KMPSearch.generateNexts(_MARKER_INPUT_CLOSE);
675
676 private static final char[] _MARKER_INPUT_OPEN = "input".toCharArray();
677
678 private static final String _MARKER_LANGUAGE = "language=\"";
679
680 private static final int[] _MARKER_LANGUAGE_NEXTS = KMPSearch.generateNexts(
681 _MARKER_LANGUAGE);
682
683 private static final String _MARKER_PRE_CLOSE = "/pre>";
684
685 private static final int[] _MARKER_PRE_CLOSE_NEXTS =
686 KMPSearch.generateNexts(_MARKER_PRE_CLOSE);
687
688 private static final char[] _MARKER_PRE_OPEN = "pre".toCharArray();
689
690 private static final String _MARKER_SCRIPT_CLOSE = "</script>";
691
692 private static final int[] _MARKER_SCRIPT_CLOSE_NEXTS =
693 KMPSearch.generateNexts(_MARKER_SCRIPT_CLOSE);
694
695 private static final char[] _MARKER_SCRIPT_OPEN = "script".toCharArray();
696
697 private static final String _MARKER_STYLE_CLOSE = "</style>";
698
699 private static final int[] _MARKER_STYLE_CLOSE_NEXTS =
700 KMPSearch.generateNexts(_MARKER_STYLE_CLOSE);
701
702 private static final char[] _MARKER_STYLE_OPEN =
703 "style type=\"text/css\">".toCharArray();
704
705 private static final String _MARKER_TEXTAREA_CLOSE = "/textarea>";
706
707 private static final int[] _MARKER_TEXTAREA_CLOSE_NEXTS =
708 KMPSearch.generateNexts(_MARKER_TEXTAREA_CLOSE);
709
710 private static final char[] _MARKER_TEXTAREA_OPEN =
711 "textarea ".toCharArray();
712
713 private static final String _MARKER_TYPE_JAVASCRIPT =
714 "type=\"text/javascript\"";
715
716 private static final int[] _MARKER_TYPE_JAVASCRIPT_NEXTS =
717 KMPSearch.generateNexts(_MARKER_TYPE_JAVASCRIPT);
718
719 private static final String _STRIP = "strip";
720
721 private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
722
723 private static Pattern _javaScriptPattern = Pattern.compile(
724 "[Jj][aA][vV][aA][sS][cC][rR][iI][pP][tT]");
725
726 private Set<String> _ignorePaths = new HashSet<String>();
727 private ConcurrentLFUCache<String, String> _minifierCache;
728
729 }