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