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