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