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