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