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.servlet.StringServletResponse;
024 import com.liferay.portal.kernel.util.CharPool;
025 import com.liferay.portal.kernel.util.ContentTypes;
026 import com.liferay.portal.kernel.util.GetterUtil;
027 import com.liferay.portal.kernel.util.HttpUtil;
028 import com.liferay.portal.kernel.util.JavaConstants;
029 import com.liferay.portal.kernel.util.KMPSearch;
030 import com.liferay.portal.kernel.util.ParamUtil;
031 import com.liferay.portal.kernel.util.Validator;
032 import com.liferay.portal.servlet.filters.BasePortalFilter;
033 import com.liferay.portal.util.MinifierUtil;
034 import com.liferay.portal.util.PropsValues;
035 import com.liferay.util.servlet.ServletResponseUtil;
036
037 import java.io.IOException;
038 import java.io.Writer;
039
040 import java.nio.CharBuffer;
041
042 import java.util.HashSet;
043 import java.util.Set;
044
045 import javax.servlet.FilterChain;
046 import javax.servlet.FilterConfig;
047 import javax.servlet.http.HttpServletRequest;
048 import javax.servlet.http.HttpServletResponse;
049
050
055 public class StripFilter extends BasePortalFilter {
056
057 public static final String SKIP_FILTER =
058 StripFilter.class.getName() + "SKIP_FILTER";
059
060 public void init(FilterConfig filterConfig) {
061 super.init(filterConfig);
062
063 for (String ignorePath : PropsValues.STRIP_IGNORE_PATHS) {
064 _ignorePaths.add(ignorePath);
065 }
066 }
067
068 protected String extractContent(CharBuffer charBuffer, int length) {
069
070
071
072
077
078 CharBuffer duplicateCharBuffer = charBuffer.duplicate();
079
080 int position = duplicateCharBuffer.position() + length;
081
082 String content = duplicateCharBuffer.limit(position).toString();
083
084 charBuffer.position(position);
085
086 return content;
087 }
088
089 protected boolean hasMarker(CharBuffer charBuffer, char[] marker) {
090 int position = charBuffer.position();
091
092 if ((position + marker.length) >= charBuffer.limit()) {
093 return false;
094 }
095
096 for (int i = 0; i < marker.length; i++) {
097 char c = marker[i];
098
099 char oldC = charBuffer.charAt(i);
100
101 if ((c != oldC) && (Character.toUpperCase(c) != oldC)) {
102 return false;
103 }
104 }
105
106 return true;
107 }
108
109 protected boolean isAlreadyFiltered(HttpServletRequest request) {
110 if (request.getAttribute(SKIP_FILTER) != null) {
111 return true;
112 }
113 else {
114 return false;
115 }
116 }
117
118 protected boolean isInclude(HttpServletRequest request) {
119 String uri = (String)request.getAttribute(
120 JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
121
122 if (uri == null) {
123 return false;
124 }
125 else {
126 return true;
127 }
128 }
129
130 protected boolean isStrip(HttpServletRequest request) {
131 if (!ParamUtil.getBoolean(request, _STRIP, true)) {
132 return false;
133 }
134
135 String path = request.getPathInfo();
136
137 if (_ignorePaths.contains(path)) {
138 if (_log.isDebugEnabled()) {
139 _log.debug("Ignore path " + path);
140 }
141
142 return false;
143 }
144
145
146
147
148
149 String lifecycle = ParamUtil.getString(request, "p_p_lifecycle");
150
151 if ((lifecycle.equals("1") &&
152 LiferayWindowState.isExclusive(request)) ||
153 lifecycle.equals("2")) {
154
155 return false;
156 }
157 else {
158 return true;
159 }
160 }
161
162 protected void outputCloseTag(
163 CharBuffer charBuffer, Writer writer, String closeTag)
164 throws IOException {
165
166 writer.write(closeTag);
167
168 charBuffer.position(charBuffer.position() + closeTag.length());
169
170 skipWhiteSpace(charBuffer, writer);
171 }
172
173 protected void outputOpenTag(
174 CharBuffer charBuffer, Writer writer, char[] openTag)
175 throws IOException {
176
177 writer.write(openTag);
178
179 charBuffer.position(charBuffer.position() + openTag.length);
180 }
181
182 protected void processCSS(
183 CharBuffer charBuffer, Writer writer)
184 throws IOException {
185
186 outputOpenTag(charBuffer, writer, _MARKER_STYLE_OPEN);
187
188 int length = KMPSearch.search(
189 charBuffer, _MARKER_STYLE_CLOSE, _MARKER_STYLE_CLOSE_NEXTS);
190
191 if (length == -1) {
192 if (_log.isWarnEnabled()) {
193 _log.warn("Missing </style>");
194 }
195
196 return;
197 }
198
199 if (length == 0) {
200 outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
201
202 return;
203 }
204
205 String content = extractContent(charBuffer, length);
206
207 String minifiedContent = content;
208
209 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
210 String key = String.valueOf(content.hashCode());
211
212 minifiedContent = _minifierCache.get(key);
213
214 if (minifiedContent == null) {
215 minifiedContent = MinifierUtil.minifyCss(content);
216
217 boolean skipCache = false;
218
219 for (String skipCss :
220 PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SKIP_CSS) {
221
222 if (minifiedContent.contains(skipCss)) {
223 skipCache = true;
224
225 break;
226 }
227 }
228
229 if (!skipCache) {
230 _minifierCache.put(key, minifiedContent);
231 }
232 }
233 }
234
235 if (!Validator.isNull(minifiedContent)) {
236 writer.write(minifiedContent);
237 }
238
239 outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
240 }
241
242 protected void processFilter(
243 HttpServletRequest request, HttpServletResponse response,
244 FilterChain filterChain)
245 throws Exception {
246
247 if (isStrip(request) && !isInclude(request) &&
248 !isAlreadyFiltered(request)) {
249
250 if (_log.isDebugEnabled()) {
251 String completeURL = HttpUtil.getCompleteURL(request);
252
253 _log.debug("Stripping " + completeURL);
254 }
255
256 request.setAttribute(SKIP_FILTER, Boolean.TRUE);
257
258 StringServletResponse stringResponse = new StringServletResponse(
259 response);
260
261 processFilter(
262 StripFilter.class, request, stringResponse, filterChain);
263
264 String contentType = GetterUtil.getString(
265 stringResponse.getContentType()).toLowerCase();
266
267 if (_log.isDebugEnabled()) {
268 _log.debug("Stripping content of type " + contentType);
269 }
270
271 response.setContentType(contentType);
272
273 if (contentType.startsWith(ContentTypes.TEXT_HTML)) {
274 CharBuffer oldCharBuffer = CharBuffer.wrap(
275 stringResponse.getString());
276
277 boolean ensureContentLength = ParamUtil.getBoolean(
278 request, _ENSURE_CONTENT_LENGTH);
279
280 if (ensureContentLength) {
281 UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
282 new UnsyncByteArrayOutputStream();
283
284 strip(
285 oldCharBuffer,
286 new OutputStreamWriter(unsyncByteArrayOutputStream));
287
288 response.setContentLength(
289 unsyncByteArrayOutputStream.size());
290
291 unsyncByteArrayOutputStream.writeTo(
292 response.getOutputStream());
293 }
294 else {
295 strip(oldCharBuffer, response.getWriter());
296 }
297 }
298 else {
299 ServletResponseUtil.write(response, stringResponse);
300 }
301 }
302 else {
303 if (_log.isDebugEnabled()) {
304 String completeURL = HttpUtil.getCompleteURL(request);
305
306 _log.debug("Not stripping " + completeURL);
307 }
308
309 processFilter(StripFilter.class, request, response, filterChain);
310 }
311 }
312
313 protected void processJavaScript(
314 CharBuffer charBuffer, Writer writer, char[] openTag)
315 throws IOException {
316
317 outputOpenTag(charBuffer, writer, openTag);
318
319 int length = KMPSearch.search(
320 charBuffer, _MARKER_SCRIPT_CLOSE, _MARKER_SCRIPT_CLOSE_NEXTS);
321
322 if (length == -1) {
323 if (_log.isWarnEnabled()) {
324 _log.warn("Missing </script>");
325 }
326
327 return;
328 }
329
330 if (length == 0) {
331 outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
332
333 return;
334 }
335
336 String content = extractContent(charBuffer, length);
337
338 String minifiedContent = content;
339
340 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
341 String key = String.valueOf(content.hashCode());
342
343 minifiedContent = _minifierCache.get(key);
344
345 if (minifiedContent == null) {
346 minifiedContent = MinifierUtil.minifyJavaScript(content);
347
348 boolean skipCache = false;
349
350 for (String skipJavaScript :
351 PropsValues.
352 MINIFIER_INLINE_CONTENT_CACHE_SKIP_JAVASCRIPT) {
353
354 if (minifiedContent.contains(skipJavaScript)) {
355 skipCache = true;
356
357 break;
358 }
359 }
360
361 if (!skipCache) {
362 _minifierCache.put(key, minifiedContent);
363 }
364 }
365 }
366
367 if (!Validator.isNull(minifiedContent)) {
368 writer.write(_CDATA_OPEN);
369 writer.write(minifiedContent);
370 writer.write(_CDATA_CLOSE);
371 }
372
373 outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
374 }
375
376 protected void processPre(CharBuffer oldCharBuffer, Writer writer)
377 throws IOException {
378
379 int length = KMPSearch.search(
380 oldCharBuffer, _MARKER_PRE_OPEN.length + 1, _MARKER_PRE_CLOSE,
381 _MARKER_PRE_CLOSE_NEXTS);
382
383 if (length == -1) {
384 if (_log.isWarnEnabled()) {
385 _log.warn("Missing </pre>");
386 }
387
388 outputOpenTag(oldCharBuffer, writer, _MARKER_PRE_OPEN);
389
390 return;
391 }
392
393 length += _MARKER_PRE_CLOSE.length();
394
395 String content = extractContent(oldCharBuffer, length);
396
397 writer.write(content);
398
399 skipWhiteSpace(oldCharBuffer, writer);
400 }
401
402 protected void processTextArea(CharBuffer oldCharBuffer, Writer writer)
403 throws IOException {
404
405 int length = KMPSearch.search(
406 oldCharBuffer, _MARKER_TEXTAREA_OPEN.length + 1,
407 _MARKER_TEXTAREA_CLOSE, _MARKER_TEXTAREA_CLOSE_NEXTS);
408
409 if (length == -1) {
410 if (_log.isWarnEnabled()) {
411 _log.warn("Missing </textArea>");
412 }
413
414 outputOpenTag(oldCharBuffer, writer, _MARKER_TEXTAREA_OPEN);
415 return;
416 }
417
418 length += _MARKER_TEXTAREA_CLOSE.length();
419
420 String content = extractContent(oldCharBuffer, length);
421
422 writer.write(content);
423
424 skipWhiteSpace(oldCharBuffer, writer);
425 }
426
427 protected boolean skipWhiteSpace(CharBuffer charBuffer, Writer writer)
428 throws IOException {
429
430 boolean skipped = false;
431
432 for (int i = charBuffer.position(); i < charBuffer.limit(); i++) {
433 char c = charBuffer.get();
434
435 if ((c == CharPool.SPACE) || (c == CharPool.TAB) ||
436 (c == CharPool.RETURN) || (c == CharPool.NEW_LINE)) {
437
438 skipped = true;
439
440 continue;
441 }
442 else {
443 charBuffer.position(i);
444
445 break;
446 }
447 }
448
449 if (skipped) {
450 writer.write(CharPool.SPACE);
451 }
452
453 return skipped;
454 }
455
456 protected void strip(CharBuffer charBuffer, Writer writer)
457 throws IOException {
458
459 skipWhiteSpace(charBuffer, writer);
460
461 while (charBuffer.hasRemaining()) {
462 char c = charBuffer.get();
463
464 writer.write(c);
465
466 if (c == CharPool.LESS_THAN) {
467 if (hasMarker(charBuffer, _MARKER_PRE_OPEN)) {
468 processPre(charBuffer, writer);
469
470 continue;
471 }
472 else if (hasMarker(charBuffer, _MARKER_TEXTAREA_OPEN)) {
473 processTextArea(charBuffer, writer);
474
475 continue;
476 }
477 else if (hasMarker(charBuffer, _MARKER_JS_OPEN)) {
478 processJavaScript(charBuffer, writer, _MARKER_JS_OPEN);
479
480 continue;
481 }
482 else if (hasMarker(charBuffer, _MARKER_SCRIPT_OPEN)) {
483 processJavaScript(charBuffer, writer, _MARKER_SCRIPT_OPEN);
484
485 continue;
486 }
487 else if (hasMarker(charBuffer, _MARKER_STYLE_OPEN)) {
488 processCSS(charBuffer, writer);
489
490 continue;
491 }
492 }
493 else if (c == CharPool.GREATER_THAN) {
494 skipWhiteSpace(charBuffer, writer);
495 }
496 }
497
498 writer.flush();
499 }
500
501 private static final String _CDATA_CLOSE = "";
502
503 private static final String _CDATA_OPEN = "";
504
505 private static final String _ENSURE_CONTENT_LENGTH = "ensureContentLength";
506
507 private static final char[] _MARKER_JS_OPEN =
508 "script type=\"text/javascript\">".toCharArray();
509
510 private static final String _MARKER_PRE_CLOSE = "/pre>";
511
512 private static final int[] _MARKER_PRE_CLOSE_NEXTS =
513 KMPSearch.generateNexts(_MARKER_PRE_CLOSE);
514
515 private static final char[] _MARKER_PRE_OPEN = "pre>".toCharArray();
516
517 private static final String _MARKER_SCRIPT_CLOSE = "</script>";
518
519 private static final int[] _MARKER_SCRIPT_CLOSE_NEXTS =
520 KMPSearch.generateNexts(_MARKER_SCRIPT_CLOSE);
521
522 private static final char[] _MARKER_SCRIPT_OPEN = "script>".toCharArray();
523
524 private static final String _MARKER_STYLE_CLOSE = "</style>";
525
526 private static final int[] _MARKER_STYLE_CLOSE_NEXTS =
527 KMPSearch.generateNexts(_MARKER_STYLE_CLOSE);
528
529 private static final char[] _MARKER_STYLE_OPEN =
530 "style type=\"text/css\">".toCharArray();
531
532 private static final String _MARKER_TEXTAREA_CLOSE = "/textarea>";
533
534 private static final int[] _MARKER_TEXTAREA_CLOSE_NEXTS =
535 KMPSearch.generateNexts(_MARKER_TEXTAREA_CLOSE);
536
537 private static final char[] _MARKER_TEXTAREA_OPEN =
538 "textarea ".toCharArray();
539
540 private static final String _STRIP = "strip";
541
542 private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
543
544 private ConcurrentLRUCache<String, String> _minifierCache =
545 new ConcurrentLRUCache<String, String>(
546 PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE);
547 private Set<String> _ignorePaths = new HashSet<String>();
548
549 }