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, null, 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 CharBuffer charBuffer, Writer writer, char[] openTag)
434 throws Exception {
435
436 int endPos = openTag.length + 1;
437
438 char c = charBuffer.charAt(openTag.length);
439
440 if (c == CharPool.SPACE) {
441 int startPos = openTag.length + 1;
442
443 for (int i = startPos; i < charBuffer.length(); i++) {
444 c = charBuffer.charAt(i);
445
446 if (c == CharPool.GREATER_THAN) {
447
448
449
450 endPos = i + 1;
451
452 int length = i - startPos;
453
454 if ((length < _MARKER_TYPE_JAVASCRIPT.length()) ||
455 (KMPSearch.search(
456 charBuffer, startPos, length,
457 _MARKER_TYPE_JAVASCRIPT,
458 _MARKER_TYPE_JAVASCRIPT_NEXTS) == -1)) {
459
460
461
462
463
464
465
466 if (!hasLanguageAttribute(
467 charBuffer, startPos, length)) {
468
469 return;
470 }
471 }
472
473
474
475
476 break;
477 }
478 else if (c == CharPool.LESS_THAN) {
479
480
481
482 return;
483 }
484 }
485
486 if (endPos == charBuffer.length()) {
487
488
489
490 return;
491 }
492 }
493 else if (c != CharPool.GREATER_THAN) {
494
495
496
497 return;
498 }
499
500 if (JavaDetector.isJDK6()) {
501 CharBuffer duplicateCharBuffer = charBuffer.duplicate();
502
503 int limit = duplicateCharBuffer.position() + endPos;
504
505 writer.append((CharSequence)duplicateCharBuffer.limit(limit));
506 }
507 else {
508 writer.append(charBuffer, 0, endPos);
509 }
510
511 charBuffer.position(charBuffer.position() + endPos);
512
513 int length = KMPSearch.search(
514 charBuffer, _MARKER_SCRIPT_CLOSE, _MARKER_SCRIPT_CLOSE_NEXTS);
515
516 if (length == -1) {
517 if (_log.isWarnEnabled()) {
518 _log.warn("Missing </script>");
519 }
520
521 return;
522 }
523
524 if (length == 0) {
525 outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
526
527 return;
528 }
529
530 String content = extractContent(charBuffer, length);
531
532 String minifiedContent = content;
533
534 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
535 CacheKeyGenerator cacheKeyGenerator =
536 CacheKeyGeneratorUtil.getCacheKeyGenerator(
537 StripFilter.class.getName());
538
539 String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
540
541 minifiedContent = _minifierCache.get(key);
542
543 if (minifiedContent == null) {
544 minifiedContent = MinifierUtil.minifyJavaScript(content);
545
546 boolean skipCache = false;
547
548 for (String skipJavaScript :
549 PropsValues.
550 MINIFIER_INLINE_CONTENT_CACHE_SKIP_JAVASCRIPT) {
551
552 if (minifiedContent.contains(skipJavaScript)) {
553 skipCache = true;
554
555 break;
556 }
557 }
558
559 if (!skipCache) {
560 _minifierCache.put(key, minifiedContent);
561 }
562 }
563 }
564
565 if (Validator.isNotNull(minifiedContent)) {
566 writer.write(minifiedContent);
567 }
568
569 outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
570 }
571
572 protected void processPre(CharBuffer oldCharBuffer, Writer writer)
573 throws Exception {
574
575 int length = KMPSearch.search(
576 oldCharBuffer, _MARKER_PRE_OPEN.length + 1, _MARKER_PRE_CLOSE,
577 _MARKER_PRE_CLOSE_NEXTS);
578
579 if (length == -1) {
580 if (_log.isWarnEnabled()) {
581 _log.warn("Missing </pre>");
582 }
583
584 outputOpenTag(oldCharBuffer, writer, _MARKER_PRE_OPEN);
585
586 return;
587 }
588
589 length += _MARKER_PRE_CLOSE.length();
590
591 String content = extractContent(oldCharBuffer, length);
592
593 writer.write(content);
594
595 skipWhiteSpace(oldCharBuffer, writer, true);
596 }
597
598 protected void processTextArea(CharBuffer oldCharBuffer, Writer writer)
599 throws Exception {
600
601 int length = KMPSearch.search(
602 oldCharBuffer, _MARKER_TEXTAREA_OPEN.length + 1,
603 _MARKER_TEXTAREA_CLOSE, _MARKER_TEXTAREA_CLOSE_NEXTS);
604
605 if (length == -1) {
606 if (_log.isWarnEnabled()) {
607 _log.warn("Missing </textArea>");
608 }
609
610 outputOpenTag(oldCharBuffer, writer, _MARKER_TEXTAREA_OPEN);
611 return;
612 }
613
614 length += _MARKER_TEXTAREA_CLOSE.length();
615
616 String content = extractContent(oldCharBuffer, length);
617
618 writer.write(content);
619
620 skipWhiteSpace(oldCharBuffer, writer, true);
621 }
622
623 protected boolean skipWhiteSpace(
624 CharBuffer charBuffer, Writer writer, boolean appendSeparator)
625 throws Exception {
626
627 boolean skipped = false;
628
629 for (int i = charBuffer.position(); i < charBuffer.limit(); i++) {
630 char c = charBuffer.get();
631
632 if ((c == CharPool.SPACE) || (c == CharPool.TAB) ||
633 (c == CharPool.RETURN) || (c == CharPool.NEW_LINE)) {
634
635 skipped = true;
636
637 continue;
638 }
639 else {
640 charBuffer.position(i);
641
642 break;
643 }
644 }
645
646 if (skipped && appendSeparator) {
647 writer.write(CharPool.SPACE);
648 }
649
650 return skipped;
651 }
652
653 protected void strip(
654 HttpServletRequest request, HttpServletResponse response,
655 CharBuffer charBuffer, Writer writer)
656 throws Exception {
657
658 skipWhiteSpace(charBuffer, writer, false);
659
660 while (charBuffer.hasRemaining()) {
661 char c = charBuffer.get();
662
663 writer.write(c);
664
665 if (c == CharPool.LESS_THAN) {
666 if (hasMarker(charBuffer, _MARKER_INPUT_OPEN)) {
667 processInput(charBuffer, writer);
668
669 continue;
670 }
671 else if (hasMarker(charBuffer, _MARKER_PRE_OPEN)) {
672 processPre(charBuffer, writer);
673
674 continue;
675 }
676 else if (hasMarker(charBuffer, _MARKER_TEXTAREA_OPEN)) {
677 processTextArea(charBuffer, writer);
678
679 continue;
680 }
681 else if (hasMarker(charBuffer, _MARKER_SCRIPT_OPEN)) {
682 processJavaScript(charBuffer, writer, _MARKER_SCRIPT_OPEN);
683
684 continue;
685 }
686 else if (hasMarker(charBuffer, _MARKER_STYLE_OPEN)) {
687 processCSS(request, response, charBuffer, writer);
688
689 continue;
690 }
691 }
692 else if (c == CharPool.GREATER_THAN) {
693 skipWhiteSpace(charBuffer, writer, true);
694 }
695
696 skipWhiteSpace(charBuffer, writer, true);
697 }
698
699 writer.flush();
700 }
701
702 private static final String _ENSURE_CONTENT_LENGTH = "ensureContentLength";
703
704 private static final String _MARKER_INPUT_CLOSE = "/>";
705
706 private static final int[] _MARKER_INPUT_CLOSE_NEXTS =
707 KMPSearch.generateNexts(_MARKER_INPUT_CLOSE);
708
709 private static final char[] _MARKER_INPUT_OPEN = "input".toCharArray();
710
711 private static final String _MARKER_LANGUAGE = "language=";
712
713 private static final int[] _MARKER_LANGUAGE_NEXTS = KMPSearch.generateNexts(
714 _MARKER_LANGUAGE);
715
716 private static final String _MARKER_PRE_CLOSE = "/pre>";
717
718 private static final int[] _MARKER_PRE_CLOSE_NEXTS =
719 KMPSearch.generateNexts(_MARKER_PRE_CLOSE);
720
721 private static final char[] _MARKER_PRE_OPEN = "pre".toCharArray();
722
723 private static final String _MARKER_SCRIPT_CLOSE = "</script>";
724
725 private static final int[] _MARKER_SCRIPT_CLOSE_NEXTS =
726 KMPSearch.generateNexts(_MARKER_SCRIPT_CLOSE);
727
728 private static final char[] _MARKER_SCRIPT_OPEN = "script".toCharArray();
729
730 private static final String _MARKER_STYLE_CLOSE = "</style>";
731
732 private static final int[] _MARKER_STYLE_CLOSE_NEXTS =
733 KMPSearch.generateNexts(_MARKER_STYLE_CLOSE);
734
735 private static final char[] _MARKER_STYLE_OPEN =
736 "style type=\"text/css\">".toCharArray();
737
738 private static final String _MARKER_TEXTAREA_CLOSE = "/textarea>";
739
740 private static final int[] _MARKER_TEXTAREA_CLOSE_NEXTS =
741 KMPSearch.generateNexts(_MARKER_TEXTAREA_CLOSE);
742
743 private static final char[] _MARKER_TEXTAREA_OPEN =
744 "textarea ".toCharArray();
745
746 private static final String _MARKER_TYPE_JAVASCRIPT =
747 "type=\"text/javascript\"";
748
749 private static final int[] _MARKER_TYPE_JAVASCRIPT_NEXTS =
750 KMPSearch.generateNexts(_MARKER_TYPE_JAVASCRIPT);
751
752 private static final String _STRIP = "strip";
753
754 private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
755
756 private static Pattern _javaScriptPattern = Pattern.compile(
757 "[Jj][aA][vV][aA][sS][cC][rR][iI][pP][tT]");
758
759 private Set<String> _ignorePaths = new HashSet<String>();
760 private ConcurrentLFUCache<String, String> _minifierCache;
761 private ServletContext _servletContext;
762
763 }