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.minifier.MinifierUtil;
039 import com.liferay.portal.servlet.filters.BasePortalFilter;
040 import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
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, request.getRequestURI(),
299 content);
300 }
301 catch (ScriptingException se) {
302 _log.error("Unable to parse SASS on CSS " + key, se);
303
304 if (_log.isDebugEnabled()) {
305 _log.debug(content);
306 }
307
308 if (response != null) {
309 response.setHeader(
310 HttpHeaders.CACHE_CONTROL,
311 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
312 }
313 }
314 }
315
316 minifiedContent = MinifierUtil.minifyCss(content);
317
318 boolean skipCache = false;
319
320 for (String skipCss :
321 PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SKIP_CSS) {
322
323 if (minifiedContent.contains(skipCss)) {
324 skipCache = true;
325
326 break;
327 }
328 }
329
330 if (!skipCache) {
331 _minifierCache.put(key, minifiedContent);
332 }
333 }
334 }
335
336 if (Validator.isNotNull(minifiedContent)) {
337 writer.write(minifiedContent);
338 }
339
340 outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
341 }
342
343 @Override
344 protected void processFilter(
345 HttpServletRequest request, HttpServletResponse response,
346 FilterChain filterChain)
347 throws Exception {
348
349 if (_log.isDebugEnabled()) {
350 String completeURL = HttpUtil.getCompleteURL(request);
351
352 _log.debug("Stripping " + completeURL);
353 }
354
355 request.setAttribute(SKIP_FILTER, Boolean.TRUE);
356
357 BufferCacheServletResponse bufferCacheServletResponse =
358 new BufferCacheServletResponse(response);
359
360 processFilter(
361 StripFilter.class, request, bufferCacheServletResponse,
362 filterChain);
363
364 String contentType = GetterUtil.getString(
365 bufferCacheServletResponse.getContentType());
366
367 contentType = StringUtil.toLowerCase(contentType);
368
369 if (_log.isDebugEnabled()) {
370 _log.debug("Stripping content of type " + contentType);
371 }
372
373 response.setContentType(contentType);
374
375 if (isStripContentType(contentType) &&
376 (bufferCacheServletResponse.getStatus() ==
377 HttpServletResponse.SC_OK)) {
378
379 CharBuffer oldCharBuffer =
380 bufferCacheServletResponse.getCharBuffer();
381
382 boolean ensureContentLength = ParamUtil.getBoolean(
383 request, _ENSURE_CONTENT_LENGTH);
384
385 if (ensureContentLength) {
386 UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
387 new UnsyncByteArrayOutputStream();
388
389 strip(
390 request, response, oldCharBuffer,
391 new OutputStreamWriter(unsyncByteArrayOutputStream));
392
393 response.setContentLength(unsyncByteArrayOutputStream.size());
394
395 unsyncByteArrayOutputStream.writeTo(response.getOutputStream());
396 }
397 else if (!response.isCommitted()) {
398 strip(request, response, oldCharBuffer, response.getWriter());
399 }
400 }
401 else {
402 ServletResponseUtil.write(response, bufferCacheServletResponse);
403 }
404 }
405
406 protected void processInput(CharBuffer oldCharBuffer, Writer writer)
407 throws Exception {
408
409 int length = KMPSearch.search(
410 oldCharBuffer, _MARKER_INPUT_OPEN.length + 1, _MARKER_INPUT_CLOSE,
411 _MARKER_INPUT_CLOSE_NEXTS);
412
413 if (length == -1) {
414 if (_log.isWarnEnabled()) {
415 _log.warn("Missing />");
416 }
417
418 outputOpenTag(oldCharBuffer, writer, _MARKER_INPUT_OPEN);
419
420 return;
421 }
422
423 length += _MARKER_INPUT_CLOSE.length();
424
425 String content = extractContent(oldCharBuffer, length);
426
427 writer.write(content);
428
429 skipWhiteSpace(oldCharBuffer, writer, true);
430 }
431
432 protected void processJavaScript(
433 String resourceName, CharBuffer charBuffer, Writer writer,
434 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 writer.append(charBuffer, 0, endPos);
502
503 charBuffer.position(charBuffer.position() + endPos);
504
505 int length = KMPSearch.search(
506 charBuffer, _MARKER_SCRIPT_CLOSE, _MARKER_SCRIPT_CLOSE_NEXTS);
507
508 if (length == -1) {
509 if (_log.isWarnEnabled()) {
510 _log.warn("Missing </script>");
511 }
512
513 return;
514 }
515
516 if (length == 0) {
517 outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
518
519 return;
520 }
521
522 String content = extractContent(charBuffer, length);
523
524 String minifiedContent = content;
525
526 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
527 CacheKeyGenerator cacheKeyGenerator =
528 CacheKeyGeneratorUtil.getCacheKeyGenerator(
529 StripFilter.class.getName());
530
531 String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
532
533 minifiedContent = _minifierCache.get(key);
534
535 if (minifiedContent == null) {
536 minifiedContent = MinifierUtil.minifyJavaScript(
537 resourceName, content);
538
539 boolean skipCache = false;
540
541 for (String skipJavaScript :
542 PropsValues.
543 MINIFIER_INLINE_CONTENT_CACHE_SKIP_JAVASCRIPT) {
544
545 if (minifiedContent.contains(skipJavaScript)) {
546 skipCache = true;
547
548 break;
549 }
550 }
551
552 if (!skipCache) {
553 _minifierCache.put(key, minifiedContent);
554 }
555 }
556 }
557
558 if (Validator.isNotNull(minifiedContent)) {
559 writer.write(minifiedContent);
560 }
561
562 outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
563 }
564
565 protected void processPre(CharBuffer oldCharBuffer, Writer writer)
566 throws Exception {
567
568 int length = KMPSearch.search(
569 oldCharBuffer, _MARKER_PRE_OPEN.length + 1, _MARKER_PRE_CLOSE,
570 _MARKER_PRE_CLOSE_NEXTS);
571
572 if (length == -1) {
573 if (_log.isWarnEnabled()) {
574 _log.warn("Missing </pre>");
575 }
576
577 outputOpenTag(oldCharBuffer, writer, _MARKER_PRE_OPEN);
578
579 return;
580 }
581
582 length += _MARKER_PRE_CLOSE.length();
583
584 String content = extractContent(oldCharBuffer, length);
585
586 writer.write(content);
587
588 skipWhiteSpace(oldCharBuffer, writer, true);
589 }
590
591 protected void processTextArea(CharBuffer oldCharBuffer, Writer writer)
592 throws Exception {
593
594 int length = KMPSearch.search(
595 oldCharBuffer, _MARKER_TEXTAREA_OPEN.length + 1,
596 _MARKER_TEXTAREA_CLOSE, _MARKER_TEXTAREA_CLOSE_NEXTS);
597
598 if (length == -1) {
599 if (_log.isWarnEnabled()) {
600 _log.warn("Missing </textArea>");
601 }
602
603 outputOpenTag(oldCharBuffer, writer, _MARKER_TEXTAREA_OPEN);
604 return;
605 }
606
607 length += _MARKER_TEXTAREA_CLOSE.length();
608
609 String content = extractContent(oldCharBuffer, length);
610
611 writer.write(content);
612
613 skipWhiteSpace(oldCharBuffer, writer, true);
614 }
615
616 protected boolean skipWhiteSpace(
617 CharBuffer charBuffer, Writer writer, boolean appendSeparator)
618 throws Exception {
619
620 boolean skipped = false;
621
622 for (int i = charBuffer.position(); i < charBuffer.limit(); i++) {
623 char c = charBuffer.get();
624
625 if ((c == CharPool.SPACE) || (c == CharPool.TAB) ||
626 (c == CharPool.RETURN) || (c == CharPool.NEW_LINE)) {
627
628 skipped = true;
629
630 continue;
631 }
632 else {
633 charBuffer.position(i);
634
635 break;
636 }
637 }
638
639 if (skipped && appendSeparator) {
640 writer.write(CharPool.SPACE);
641 }
642
643 return skipped;
644 }
645
646 protected void strip(
647 HttpServletRequest request, HttpServletResponse response,
648 CharBuffer charBuffer, Writer writer)
649 throws Exception {
650
651 skipWhiteSpace(charBuffer, writer, false);
652
653 while (charBuffer.hasRemaining()) {
654 char c = charBuffer.get();
655
656 writer.write(c);
657
658 if (c == CharPool.LESS_THAN) {
659 if (hasMarker(charBuffer, _MARKER_INPUT_OPEN)) {
660 processInput(charBuffer, writer);
661
662 continue;
663 }
664 else if (hasMarker(charBuffer, _MARKER_PRE_OPEN)) {
665 processPre(charBuffer, writer);
666
667 continue;
668 }
669 else if (hasMarker(charBuffer, _MARKER_TEXTAREA_OPEN)) {
670 processTextArea(charBuffer, writer);
671
672 continue;
673 }
674 else if (hasMarker(charBuffer, _MARKER_SCRIPT_OPEN)) {
675 StringBuffer requestURL = request.getRequestURL();
676
677 processJavaScript(
678 requestURL.toString(), charBuffer, writer,
679 _MARKER_SCRIPT_OPEN);
680
681 continue;
682 }
683 else if (hasMarker(charBuffer, _MARKER_STYLE_OPEN)) {
684 processCSS(request, response, charBuffer, writer);
685
686 continue;
687 }
688 }
689 else if (c == CharPool.GREATER_THAN) {
690 skipWhiteSpace(charBuffer, writer, true);
691 }
692
693 skipWhiteSpace(charBuffer, writer, true);
694 }
695
696 writer.flush();
697 }
698
699 private static final String _ENSURE_CONTENT_LENGTH = "ensureContentLength";
700
701 private static final String _MARKER_INPUT_CLOSE = "/>";
702
703 private static final int[] _MARKER_INPUT_CLOSE_NEXTS =
704 KMPSearch.generateNexts(_MARKER_INPUT_CLOSE);
705
706 private static final char[] _MARKER_INPUT_OPEN = "input".toCharArray();
707
708 private static final String _MARKER_LANGUAGE = "language=";
709
710 private static final int[] _MARKER_LANGUAGE_NEXTS = KMPSearch.generateNexts(
711 _MARKER_LANGUAGE);
712
713 private static final String _MARKER_PRE_CLOSE = "/pre>";
714
715 private static final int[] _MARKER_PRE_CLOSE_NEXTS =
716 KMPSearch.generateNexts(_MARKER_PRE_CLOSE);
717
718 private static final char[] _MARKER_PRE_OPEN = "pre".toCharArray();
719
720 private static final String _MARKER_SCRIPT_CLOSE = "</script>";
721
722 private static final int[] _MARKER_SCRIPT_CLOSE_NEXTS =
723 KMPSearch.generateNexts(_MARKER_SCRIPT_CLOSE);
724
725 private static final char[] _MARKER_SCRIPT_OPEN = "script".toCharArray();
726
727 private static final String _MARKER_STYLE_CLOSE = "</style>";
728
729 private static final int[] _MARKER_STYLE_CLOSE_NEXTS =
730 KMPSearch.generateNexts(_MARKER_STYLE_CLOSE);
731
732 private static final char[] _MARKER_STYLE_OPEN =
733 "style type=\"text/css\">".toCharArray();
734
735 private static final String _MARKER_TEXTAREA_CLOSE = "/textarea>";
736
737 private static final int[] _MARKER_TEXTAREA_CLOSE_NEXTS =
738 KMPSearch.generateNexts(_MARKER_TEXTAREA_CLOSE);
739
740 private static final char[] _MARKER_TEXTAREA_OPEN =
741 "textarea ".toCharArray();
742
743 private static final String _MARKER_TYPE_JAVASCRIPT =
744 "type=\"text/javascript\"";
745
746 private static final int[] _MARKER_TYPE_JAVASCRIPT_NEXTS =
747 KMPSearch.generateNexts(_MARKER_TYPE_JAVASCRIPT);
748
749 private static final String _STRIP = "strip";
750
751 private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
752
753 private static Pattern _javaScriptPattern = Pattern.compile(
754 "[Jj][aA][vV][aA][sS][cC][rR][iI][pP][tT]");
755
756 private Set<String> _ignorePaths = new HashSet<String>();
757 private ConcurrentLFUCache<String, String> _minifierCache;
758 private ServletContext _servletContext;
759
760 }