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