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.GetterUtil;
031 import com.liferay.portal.kernel.util.HttpUtil;
032 import com.liferay.portal.kernel.util.JavaConstants;
033 import com.liferay.portal.kernel.util.KMPSearch;
034 import com.liferay.portal.kernel.util.ParamUtil;
035 import com.liferay.portal.kernel.util.StringPool;
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 boolean isStripContentType(String contentType) {
214 for (String stripContentType : PropsValues.STRIP_MIME_TYPES) {
215 if (stripContentType.endsWith(StringPool.STAR)) {
216 stripContentType = stripContentType.substring(
217 0, stripContentType.length() - 1);
218
219 if (contentType.startsWith(stripContentType)) {
220 return true;
221 }
222 }
223 else {
224 if (contentType.equals(stripContentType)) {
225 return true;
226 }
227 }
228 }
229
230 return false;
231 }
232
233 protected void outputCloseTag(
234 CharBuffer charBuffer, Writer writer, String closeTag)
235 throws Exception {
236
237 writer.write(closeTag);
238
239 charBuffer.position(charBuffer.position() + closeTag.length());
240
241 skipWhiteSpace(charBuffer, writer, true);
242 }
243
244 protected void outputOpenTag(
245 CharBuffer charBuffer, Writer writer, char[] openTag)
246 throws Exception {
247
248 writer.write(openTag);
249
250 charBuffer.position(charBuffer.position() + openTag.length);
251 }
252
253 protected void processCSS(
254 HttpServletRequest request, HttpServletResponse response,
255 CharBuffer charBuffer, Writer writer)
256 throws Exception {
257
258 outputOpenTag(charBuffer, writer, _MARKER_STYLE_OPEN);
259
260 int length = KMPSearch.search(
261 charBuffer, _MARKER_STYLE_CLOSE, _MARKER_STYLE_CLOSE_NEXTS);
262
263 if (length == -1) {
264 if (_log.isWarnEnabled()) {
265 _log.warn("Missing </style>");
266 }
267
268 return;
269 }
270
271 if (length == 0) {
272 outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
273
274 return;
275 }
276
277 String content = extractContent(charBuffer, length);
278
279 String minifiedContent = content;
280
281 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
282 CacheKeyGenerator cacheKeyGenerator =
283 CacheKeyGeneratorUtil.getCacheKeyGenerator(
284 StripFilter.class.getName());
285
286 String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
287
288 minifiedContent = _minifierCache.get(key);
289
290 if (minifiedContent == null) {
291 if (PropsValues.STRIP_CSS_SASS_ENABLED) {
292 try {
293 content = DynamicCSSUtil.parseSass(
294 request, key, content);
295 }
296 catch (ScriptingException se) {
297 _log.error("Unable to parse SASS on CSS " + key, se);
298
299 if (_log.isDebugEnabled()) {
300 _log.debug(content);
301 }
302
303 if (response != null) {
304 response.setHeader(
305 HttpHeaders.CACHE_CONTROL,
306 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
307 }
308 }
309 }
310
311 minifiedContent = MinifierUtil.minifyCss(content);
312
313 boolean skipCache = false;
314
315 for (String skipCss :
316 PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SKIP_CSS) {
317
318 if (minifiedContent.contains(skipCss)) {
319 skipCache = true;
320
321 break;
322 }
323 }
324
325 if (!skipCache) {
326 _minifierCache.put(key, minifiedContent);
327 }
328 }
329 }
330
331 if (Validator.isNotNull(minifiedContent)) {
332 writer.write(minifiedContent);
333 }
334
335 outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
336 }
337
338 @Override
339 protected void processFilter(
340 HttpServletRequest request, HttpServletResponse response,
341 FilterChain filterChain)
342 throws Exception {
343
344 if (_log.isDebugEnabled()) {
345 String completeURL = HttpUtil.getCompleteURL(request);
346
347 _log.debug("Stripping " + completeURL);
348 }
349
350 request.setAttribute(SKIP_FILTER, Boolean.TRUE);
351
352 StringServletResponse stringResponse = new StringServletResponse(
353 response);
354
355 processFilter(StripFilter.class, request, stringResponse, filterChain);
356
357 String contentType = GetterUtil.getString(
358 stringResponse.getContentType()).toLowerCase();
359
360 if (_log.isDebugEnabled()) {
361 _log.debug("Stripping content of type " + contentType);
362 }
363
364 response.setContentType(contentType);
365
366 if (isStripContentType(contentType) &&
367 (stringResponse.getStatus() == HttpServletResponse.SC_OK)) {
368
369 CharBuffer oldCharBuffer = CharBuffer.wrap(
370 stringResponse.getString());
371
372 boolean ensureContentLength = ParamUtil.getBoolean(
373 request, _ENSURE_CONTENT_LENGTH);
374
375 if (ensureContentLength) {
376 UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
377 new UnsyncByteArrayOutputStream();
378
379 strip(
380 request, response, oldCharBuffer,
381 new OutputStreamWriter(unsyncByteArrayOutputStream));
382
383 response.setContentLength(unsyncByteArrayOutputStream.size());
384
385 unsyncByteArrayOutputStream.writeTo(response.getOutputStream());
386 }
387 else if (!response.isCommitted()) {
388 strip(request, response, oldCharBuffer, response.getWriter());
389 }
390 }
391 else {
392 ServletResponseUtil.write(response, stringResponse);
393 }
394 }
395
396 protected void processInput(CharBuffer oldCharBuffer, Writer writer)
397 throws Exception {
398
399 int length = KMPSearch.search(
400 oldCharBuffer, _MARKER_INPUT_OPEN.length + 1, _MARKER_INPUT_CLOSE,
401 _MARKER_INPUT_CLOSE_NEXTS);
402
403 if (length == -1) {
404 if (_log.isWarnEnabled()) {
405 _log.warn("Missing />");
406 }
407
408 outputOpenTag(oldCharBuffer, writer, _MARKER_INPUT_OPEN);
409
410 return;
411 }
412
413 length += _MARKER_INPUT_CLOSE.length();
414
415 String content = extractContent(oldCharBuffer, length);
416
417 writer.write(content);
418
419 skipWhiteSpace(oldCharBuffer, writer, true);
420 }
421
422 protected void processJavaScript(
423 CharBuffer charBuffer, Writer writer, char[] openTag)
424 throws Exception {
425
426 int endPos = openTag.length + 1;
427
428 char c = charBuffer.charAt(openTag.length);
429
430 if (c == CharPool.SPACE) {
431 int startPos = openTag.length + 1;
432
433 for (int i = startPos; i < charBuffer.length(); i++) {
434 c = charBuffer.charAt(i);
435
436 if (c == CharPool.GREATER_THAN) {
437
438
439
440 endPos = i + 1;
441
442 int length = i - startPos;
443
444 if ((length < _MARKER_TYPE_JAVASCRIPT.length()) ||
445 (KMPSearch.search(
446 charBuffer, startPos, length,
447 _MARKER_TYPE_JAVASCRIPT,
448 _MARKER_TYPE_JAVASCRIPT_NEXTS) == -1)) {
449
450
451
452
453
454
455
456 if (!hasLanguageAttribute(
457 charBuffer, startPos, length)) {
458
459 return;
460 }
461 }
462
463
464
465
466 break;
467 }
468 else if (c == CharPool.LESS_THAN) {
469
470
471
472 return;
473 }
474 }
475
476 if (endPos == charBuffer.length()) {
477
478
479
480 return;
481 }
482 }
483 else if (c != CharPool.GREATER_THAN) {
484
485
486
487 return;
488 }
489
490 writer.append(charBuffer, 0, endPos);
491
492 charBuffer.position(charBuffer.position() + endPos);
493
494 int length = KMPSearch.search(
495 charBuffer, _MARKER_SCRIPT_CLOSE, _MARKER_SCRIPT_CLOSE_NEXTS);
496
497 if (length == -1) {
498 if (_log.isWarnEnabled()) {
499 _log.warn("Missing </script>");
500 }
501
502 return;
503 }
504
505 if (length == 0) {
506 outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
507
508 return;
509 }
510
511 String content = extractContent(charBuffer, length);
512
513 String minifiedContent = content;
514
515 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
516 CacheKeyGenerator cacheKeyGenerator =
517 CacheKeyGeneratorUtil.getCacheKeyGenerator(
518 StripFilter.class.getName());
519
520 String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
521
522 minifiedContent = _minifierCache.get(key);
523
524 if (minifiedContent == null) {
525 minifiedContent = MinifierUtil.minifyJavaScript(content);
526
527 boolean skipCache = false;
528
529 for (String skipJavaScript :
530 PropsValues.
531 MINIFIER_INLINE_CONTENT_CACHE_SKIP_JAVASCRIPT) {
532
533 if (minifiedContent.contains(skipJavaScript)) {
534 skipCache = true;
535
536 break;
537 }
538 }
539
540 if (!skipCache) {
541 _minifierCache.put(key, minifiedContent);
542 }
543 }
544 }
545
546 if (Validator.isNotNull(minifiedContent)) {
547 writer.write(minifiedContent);
548 }
549
550 outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
551 }
552
553 protected void processPre(CharBuffer oldCharBuffer, Writer writer)
554 throws Exception {
555
556 int length = KMPSearch.search(
557 oldCharBuffer, _MARKER_PRE_OPEN.length + 1, _MARKER_PRE_CLOSE,
558 _MARKER_PRE_CLOSE_NEXTS);
559
560 if (length == -1) {
561 if (_log.isWarnEnabled()) {
562 _log.warn("Missing </pre>");
563 }
564
565 outputOpenTag(oldCharBuffer, writer, _MARKER_PRE_OPEN);
566
567 return;
568 }
569
570 length += _MARKER_PRE_CLOSE.length();
571
572 String content = extractContent(oldCharBuffer, length);
573
574 writer.write(content);
575
576 skipWhiteSpace(oldCharBuffer, writer, true);
577 }
578
579 protected void processTextArea(CharBuffer oldCharBuffer, Writer writer)
580 throws Exception {
581
582 int length = KMPSearch.search(
583 oldCharBuffer, _MARKER_TEXTAREA_OPEN.length + 1,
584 _MARKER_TEXTAREA_CLOSE, _MARKER_TEXTAREA_CLOSE_NEXTS);
585
586 if (length == -1) {
587 if (_log.isWarnEnabled()) {
588 _log.warn("Missing </textArea>");
589 }
590
591 outputOpenTag(oldCharBuffer, writer, _MARKER_TEXTAREA_OPEN);
592 return;
593 }
594
595 length += _MARKER_TEXTAREA_CLOSE.length();
596
597 String content = extractContent(oldCharBuffer, length);
598
599 writer.write(content);
600
601 skipWhiteSpace(oldCharBuffer, writer, true);
602 }
603
604 protected boolean skipWhiteSpace(
605 CharBuffer charBuffer, Writer writer, boolean appendSeparator)
606 throws Exception {
607
608 boolean skipped = false;
609
610 for (int i = charBuffer.position(); i < charBuffer.limit(); i++) {
611 char c = charBuffer.get();
612
613 if ((c == CharPool.SPACE) || (c == CharPool.TAB) ||
614 (c == CharPool.RETURN) || (c == CharPool.NEW_LINE)) {
615
616 skipped = true;
617
618 continue;
619 }
620 else {
621 charBuffer.position(i);
622
623 break;
624 }
625 }
626
627 if (skipped && appendSeparator) {
628 writer.write(CharPool.SPACE);
629 }
630
631 return skipped;
632 }
633
634 protected void strip(
635 HttpServletRequest request, HttpServletResponse response,
636 CharBuffer charBuffer, Writer writer)
637 throws Exception {
638
639 skipWhiteSpace(charBuffer, writer, false);
640
641 while (charBuffer.hasRemaining()) {
642 char c = charBuffer.get();
643
644 writer.write(c);
645
646 if (c == CharPool.LESS_THAN) {
647 if (hasMarker(charBuffer, _MARKER_INPUT_OPEN)) {
648 processInput(charBuffer, writer);
649
650 continue;
651 }
652 else if (hasMarker(charBuffer, _MARKER_PRE_OPEN)) {
653 processPre(charBuffer, writer);
654
655 continue;
656 }
657 else if (hasMarker(charBuffer, _MARKER_TEXTAREA_OPEN)) {
658 processTextArea(charBuffer, writer);
659
660 continue;
661 }
662 else if (hasMarker(charBuffer, _MARKER_SCRIPT_OPEN)) {
663 processJavaScript(charBuffer, writer, _MARKER_SCRIPT_OPEN);
664
665 continue;
666 }
667 else if (hasMarker(charBuffer, _MARKER_STYLE_OPEN)) {
668 processCSS(request, response, charBuffer, writer);
669
670 continue;
671 }
672 }
673 else if (c == CharPool.GREATER_THAN) {
674 skipWhiteSpace(charBuffer, writer, true);
675 }
676
677 skipWhiteSpace(charBuffer, writer, true);
678 }
679
680 writer.flush();
681 }
682
683 private static final String _ENSURE_CONTENT_LENGTH = "ensureContentLength";
684
685 private static final String _MARKER_INPUT_CLOSE = "/>";
686
687 private static final int[] _MARKER_INPUT_CLOSE_NEXTS =
688 KMPSearch.generateNexts(_MARKER_INPUT_CLOSE);
689
690 private static final char[] _MARKER_INPUT_OPEN = "input".toCharArray();
691
692 private static final String _MARKER_LANGUAGE = "language=";
693
694 private static final int[] _MARKER_LANGUAGE_NEXTS = KMPSearch.generateNexts(
695 _MARKER_LANGUAGE);
696
697 private static final String _MARKER_PRE_CLOSE = "/pre>";
698
699 private static final int[] _MARKER_PRE_CLOSE_NEXTS =
700 KMPSearch.generateNexts(_MARKER_PRE_CLOSE);
701
702 private static final char[] _MARKER_PRE_OPEN = "pre".toCharArray();
703
704 private static final String _MARKER_SCRIPT_CLOSE = "</script>";
705
706 private static final int[] _MARKER_SCRIPT_CLOSE_NEXTS =
707 KMPSearch.generateNexts(_MARKER_SCRIPT_CLOSE);
708
709 private static final char[] _MARKER_SCRIPT_OPEN = "script".toCharArray();
710
711 private static final String _MARKER_STYLE_CLOSE = "</style>";
712
713 private static final int[] _MARKER_STYLE_CLOSE_NEXTS =
714 KMPSearch.generateNexts(_MARKER_STYLE_CLOSE);
715
716 private static final char[] _MARKER_STYLE_OPEN =
717 "style type=\"text/css\">".toCharArray();
718
719 private static final String _MARKER_TEXTAREA_CLOSE = "/textarea>";
720
721 private static final int[] _MARKER_TEXTAREA_CLOSE_NEXTS =
722 KMPSearch.generateNexts(_MARKER_TEXTAREA_CLOSE);
723
724 private static final char[] _MARKER_TEXTAREA_OPEN =
725 "textarea ".toCharArray();
726
727 private static final String _MARKER_TYPE_JAVASCRIPT =
728 "type=\"text/javascript\"";
729
730 private static final int[] _MARKER_TYPE_JAVASCRIPT_NEXTS =
731 KMPSearch.generateNexts(_MARKER_TYPE_JAVASCRIPT);
732
733 private static final String _STRIP = "strip";
734
735 private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
736
737 private static Pattern _javaScriptPattern = Pattern.compile(
738 "[Jj][aA][vV][aA][sS][cC][rR][iI][pP][tT]");
739
740 private Set<String> _ignorePaths = new HashSet<String>();
741 private ConcurrentLFUCache<String, String> _minifierCache;
742
743 }