1
14
15 package com.liferay.portal.servlet.filters.strip;
16
17 import com.liferay.portal.kernel.concurrent.ConcurrentLRUCache;
18 import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
19 import com.liferay.portal.kernel.log.Log;
20 import com.liferay.portal.kernel.log.LogFactoryUtil;
21 import com.liferay.portal.kernel.nio.charset.CharsetEncoderUtil;
22 import com.liferay.portal.kernel.portlet.LiferayWindowState;
23 import com.liferay.portal.kernel.servlet.StringServletResponse;
24 import com.liferay.portal.kernel.util.CharPool;
25 import com.liferay.portal.kernel.util.GetterUtil;
26 import com.liferay.portal.kernel.util.HttpUtil;
27 import com.liferay.portal.kernel.util.JavaConstants;
28 import com.liferay.portal.kernel.util.KMPSearch;
29 import com.liferay.portal.kernel.util.ParamUtil;
30 import com.liferay.portal.kernel.util.StringPool;
31 import com.liferay.portal.kernel.util.Validator;
32 import com.liferay.portal.servlet.filters.BasePortalFilter;
33 import com.liferay.portal.util.MinifierUtil;
34 import com.liferay.portal.util.PropsValues;
35 import com.liferay.util.servlet.ServletResponseUtil;
36
37 import java.io.IOException;
38 import java.io.OutputStream;
39
40 import java.nio.ByteBuffer;
41
42 import java.util.HashSet;
43 import java.util.Set;
44
45 import javax.servlet.FilterChain;
46 import javax.servlet.FilterConfig;
47 import javax.servlet.http.HttpServletRequest;
48 import javax.servlet.http.HttpServletResponse;
49
50
57 public class StripFilter extends BasePortalFilter {
58
59 public static final String SKIP_FILTER =
60 StripFilter.class.getName() + "SKIP_FILTER";
61
62 public void init(FilterConfig filterConfig) {
63 super.init(filterConfig);
64
65 for (String ignorePath : PropsValues.STRIP_IGNORE_PATHS) {
66 _ignorePaths.add(ignorePath);
67 }
68 }
69
70 protected int countContinuousWhiteSpace(byte[] oldByteArray, int offset) {
71 int count = 0;
72
73 for (int i = offset ; i < oldByteArray.length ; i++) {
74 char c = (char)oldByteArray[i];
75
76 if ((c == CharPool.SPACE) || (c == CharPool.TAB) ||
77 (c == CharPool.RETURN) || (c == CharPool.NEW_LINE)) {
78
79 count++;
80 }
81 else{
82 return count;
83 }
84 }
85
86 return count;
87 }
88
89 protected boolean hasMarker(byte[] oldByteArray, int pos, byte[] marker) {
90 if ((pos + marker.length) >= oldByteArray.length) {
91 return false;
92 }
93
94 for (int i = 0; i < marker.length; i++) {
95 byte c = marker[i];
96
97 byte oldC = oldByteArray[pos + i + 1];
98
99 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
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 int processCSS(
161 byte[] oldByteArray, OutputStream newBytes, int currentIndex)
162 throws IOException {
163
164 int beginIndex = currentIndex + _MARKER_STYLE_OPEN.length + 1;
165
166 int endIndex = KMPSearch.search(
167 oldByteArray, beginIndex, _MARKER_STYLE_CLOSE,
168 _MARKER_STYLE_CLOSE_NEXTS);
169
170 if (endIndex == -1) {
171 _log.error("Missing </style>");
172
173 return currentIndex + 1;
174 }
175
176 int newBeginIndex = endIndex + _MARKER_STYLE_CLOSE.length;
177
178 newBeginIndex += countContinuousWhiteSpace(oldByteArray, newBeginIndex);
179
180 String content = new String(
181 oldByteArray, beginIndex, endIndex - beginIndex);
182
183 if (Validator.isNull(content)) {
184 return newBeginIndex;
185 }
186
187 String minifiedContent = content;
188
189 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
190 String key = String.valueOf(content.hashCode());
191
192 minifiedContent = _minifierCache.get(key);
193
194 if (minifiedContent == null) {
195 minifiedContent = MinifierUtil.minifyCss(content);
196
197 boolean skipCache = false;
198
199 for (String skipCss :
200 PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SKIP_CSS) {
201
202 if (minifiedContent.contains(skipCss)) {
203 skipCache = true;
204
205 break;
206 }
207 }
208
209 if (!skipCache) {
210 _minifierCache.put(key, minifiedContent);
211 }
212 }
213 }
214
215 if (Validator.isNull(minifiedContent)) {
216 return newBeginIndex;
217 }
218
219 ByteBuffer contentByteBuffer = CharsetEncoderUtil.encode(
220 StringPool.UTF8, minifiedContent);
221
222 newBytes.write(_STYLE_TYPE_CSS);
223 newBytes.write(contentByteBuffer.array(), 0, contentByteBuffer.limit());
224 newBytes.write(_MARKER_STYLE_CLOSE);
225
226 return newBeginIndex;
227 }
228
229 protected void processFilter(
230 HttpServletRequest request, HttpServletResponse response,
231 FilterChain filterChain)
232 throws Exception {
233
234 if (isStrip(request) && !isInclude(request) &&
235 !isAlreadyFiltered(request)) {
236
237 if (_log.isDebugEnabled()) {
238 String completeURL = HttpUtil.getCompleteURL(request);
239
240 _log.debug("Stripping " + completeURL);
241 }
242
243 request.setAttribute(SKIP_FILTER, Boolean.TRUE);
244
245 StringServletResponse stringResponse = new StringServletResponse(
246 response);
247
248 processFilter(
249 StripFilter.class, request, stringResponse, filterChain);
250
251 String contentType = GetterUtil.getString(
252 stringResponse.getContentType()).toLowerCase();
253
254 if (_log.isDebugEnabled()) {
255 _log.debug("Stripping content of type " + contentType);
256 }
257
258 response.setContentType(contentType);
259
260 if (contentType.indexOf("text/") != -1) {
261 byte[] oldByteArray = null;
262 int length = 0;
263
264 if (stringResponse.isCalledGetOutputStream()) {
265 UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
266 stringResponse.getUnsyncByteArrayOutputStream();
267
268 oldByteArray =
269 unsyncByteArrayOutputStream.unsafeGetByteArray();
270 length = unsyncByteArrayOutputStream.size();
271 }
272 else {
273 String content = stringResponse.getString();
274
275 ByteBuffer contentByteBuffer = CharsetEncoderUtil.encode(
276 StringPool.UTF8, content);
277
278 oldByteArray = contentByteBuffer.array();
279 length = contentByteBuffer.limit();
280 }
281
282 UnsyncByteArrayOutputStream outputStream =
283 new UnsyncByteArrayOutputStream(
284 (int)(length * _COMPRESSION_RATE));
285
286 strip(oldByteArray, length, outputStream);
287
288 ServletResponseUtil.write(
289 response, outputStream.unsafeGetByteArray(),
290 outputStream.size());
291 }
292 else {
293 ServletResponseUtil.write(response, stringResponse);
294 }
295 }
296 else {
297 if (_log.isDebugEnabled()) {
298 String completeURL = HttpUtil.getCompleteURL(request);
299
300 _log.debug("Not stripping " + completeURL);
301 }
302
303 processFilter(StripFilter.class, request, response, filterChain);
304 }
305 }
306
307 protected int processJavaScript(
308 byte[] oldByteArray, OutputStream newBytes, int currentIndex,
309 byte[] openTag)
310 throws IOException {
311
312 int beginIndex = currentIndex + openTag.length + 1;
313
314 int endIndex = KMPSearch.search(
315 oldByteArray, beginIndex, _MARKER_SCRIPT_CLOSE,
316 _MARKER_SCRIPT_CLOSE_NEXTS);
317
318 if (endIndex == -1) {
319 _log.error("Missing </script>");
320
321 return currentIndex + 1;
322 }
323
324 int newBeginIndex = endIndex + _MARKER_SCRIPT_CLOSE.length;
325
326 newBeginIndex += countContinuousWhiteSpace(oldByteArray, newBeginIndex);
327
328 String content = new String(
329 oldByteArray, beginIndex, endIndex - beginIndex);
330
331 if (Validator.isNull(content)) {
332 return newBeginIndex;
333 }
334
335 String minifiedContent = content;
336
337 if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
338 String key = String.valueOf(content.hashCode());
339
340 minifiedContent = _minifierCache.get(key);
341
342 if (minifiedContent == null) {
343 minifiedContent = MinifierUtil.minifyJavaScript(content);
344
345 boolean skipCache = false;
346
347 for (String skipJavaScript :
348 PropsValues.
349 MINIFIER_INLINE_CONTENT_CACHE_SKIP_JAVASCRIPT) {
350
351 if (minifiedContent.contains(skipJavaScript)) {
352 skipCache = true;
353
354 break;
355 }
356 }
357
358 if (!skipCache) {
359 _minifierCache.put(key, minifiedContent);
360 }
361 }
362 }
363
364 if (Validator.isNull(minifiedContent)) {
365 return newBeginIndex;
366 }
367
368 ByteBuffer contentByteBuffer = CharsetEncoderUtil.encode(
369 StringPool.UTF8, minifiedContent);
370
371 newBytes.write(_SCRIPT_TYPE_JAVASCRIPT);
372 newBytes.write(_CDATA_OPEN);
373 newBytes.write(contentByteBuffer.array(), 0, contentByteBuffer.limit());
374 newBytes.write(_CDATA_CLOSE);
375 newBytes.write(_MARKER_SCRIPT_CLOSE);
376
377 return newBeginIndex;
378 }
379
380 protected int processPre(
381 byte[] oldByteArray, OutputStream newBytes, int currentIndex)
382 throws IOException {
383
384 int beginIndex = currentIndex + _MARKER_PRE_OPEN.length + 1;
385
386 int endIndex = KMPSearch.search(
387 oldByteArray, beginIndex, _MARKER_PRE_CLOSE,
388 _MARKER_PRE_CLOSE_NEXTS);
389
390 if (endIndex == -1) {
391 _log.error("Missing </pre>");
392
393 return currentIndex + 1;
394 }
395
396 int newBeginIndex = endIndex + _MARKER_PRE_CLOSE.length;
397
398 newBytes.write(
399 oldByteArray, currentIndex, newBeginIndex - currentIndex);
400
401 newBeginIndex += countContinuousWhiteSpace(oldByteArray, newBeginIndex);
402
403 return newBeginIndex;
404 }
405
406 protected int processTextArea(
407 byte[] oldByteArray, OutputStream newBytes, int currentIndex)
408 throws IOException {
409
410 int beginIndex = currentIndex + _MARKER_TEXTAREA_OPEN.length + 1;
411
412 int endIndex = KMPSearch.search(
413 oldByteArray, beginIndex, _MARKER_TEXTAREA_CLOSE,
414 _MARKER_TEXTAREA_CLOSE_NEXTS);
415
416 if (endIndex == -1) {
417 _log.error("Missing </textArea>");
418
419 return currentIndex + 1;
420 }
421
422 int newBeginIndex = endIndex + _MARKER_TEXTAREA_CLOSE.length;
423
424 newBytes.write(
425 oldByteArray, currentIndex, newBeginIndex - currentIndex);
426
427 newBeginIndex += countContinuousWhiteSpace(oldByteArray, newBeginIndex);
428
429 return newBeginIndex;
430 }
431
432 protected void strip(
433 byte[] oldByteArray, int length, OutputStream outputStream)
434 throws IOException {
435
436 int count = countContinuousWhiteSpace(oldByteArray, 0);
437
438 for (int i = count; i < length; i++) {
439 byte b = oldByteArray[i];
440
441 if (b == CharPool.LESS_THAN) {
442 if (hasMarker(oldByteArray, i, _MARKER_PRE_OPEN)) {
443 i = processPre(oldByteArray, outputStream, i) - 1;
444
445 continue;
446 }
447 else if (hasMarker(oldByteArray, i, _MARKER_TEXTAREA_OPEN)) {
448 i = processTextArea(oldByteArray, outputStream, i) - 1;
449
450 continue;
451 }
452 else if (hasMarker(oldByteArray, i, _MARKER_JS_OPEN)) {
453 i = processJavaScript(
454 oldByteArray, outputStream, i, _MARKER_JS_OPEN) - 1;
455
456 continue;
457 }
458 else if (hasMarker(oldByteArray, i, _MARKER_SCRIPT_OPEN)) {
459 i = processJavaScript(
460 oldByteArray, outputStream, i,
461 _MARKER_SCRIPT_OPEN) - 1;
462
463 continue;
464 }
465 else if (hasMarker(oldByteArray, i, _MARKER_STYLE_OPEN)) {
466 i = processCSS(oldByteArray, outputStream, i) - 1;
467
468 continue;
469 }
470 }
471 else if (b == CharPool.GREATER_THAN) {
472 outputStream.write(b);
473
474 int spaceCount = countContinuousWhiteSpace(oldByteArray, i + 1);
475
476 if (spaceCount > 0) {
477 i = i + spaceCount;
478
479 outputStream.write(CharPool.SPACE);
480 }
481
482 continue;
483 }
484
485 int spaceCount = countContinuousWhiteSpace(oldByteArray, i);
486
487 if (spaceCount > 0) {
488 outputStream.write(CharPool.SPACE);
489
490 i = i + spaceCount - 1;
491 }
492 else {
493 outputStream.write(b);
494 }
495 }
496
497 outputStream.flush();
498 }
499
500 private static final byte[] _CDATA_CLOSE = "/*]]>*/".getBytes();
501
502 private static final byte[] _CDATA_OPEN = "/*<![CDATA[*/".getBytes();
503
504 private static final double _COMPRESSION_RATE = 0.7;
505
506 private static final byte[] _MARKER_JS_OPEN =
507 "script type=\"text/javascript\">".getBytes();
508
509 private static final byte[] _MARKER_PRE_CLOSE = "/pre>".getBytes();
510
511 private static final int[] _MARKER_PRE_CLOSE_NEXTS =
512 KMPSearch.generateNexts(_MARKER_PRE_CLOSE);
513
514 private static final byte[] _MARKER_PRE_OPEN = "pre>".getBytes();
515
516 private static final byte[] _MARKER_SCRIPT_CLOSE = "</script>".getBytes();
517
518 private static final int[] _MARKER_SCRIPT_CLOSE_NEXTS =
519 KMPSearch.generateNexts(_MARKER_SCRIPT_CLOSE);
520
521 private static final byte[] _MARKER_SCRIPT_OPEN = "script>".getBytes();
522
523 private static final byte[] _MARKER_STYLE_CLOSE = "</style>".getBytes();
524
525 private static final int[] _MARKER_STYLE_CLOSE_NEXTS =
526 KMPSearch.generateNexts(_MARKER_STYLE_CLOSE);
527
528 private static final byte[] _MARKER_STYLE_OPEN =
529 "style type=\"text/css\">".getBytes();
530
531 private static final byte[] _MARKER_TEXTAREA_CLOSE =
532 "/textarea>".getBytes();
533
534 private static final int[] _MARKER_TEXTAREA_CLOSE_NEXTS =
535 KMPSearch.generateNexts(_MARKER_TEXTAREA_CLOSE);
536
537 private static final byte[] _MARKER_TEXTAREA_OPEN =
538 "textarea ".getBytes();
539
540 private static final byte[] _SCRIPT_TYPE_JAVASCRIPT =
541 "<script type=\"text/javascript\">".getBytes();
542
543 private static final String _STRIP = "strip";
544
545 private static final byte[] _STYLE_TYPE_CSS =
546 "<style type=\"text/css\">".getBytes();
547
548 private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
549
550 private ConcurrentLRUCache<String, String> _minifierCache =
551 new ConcurrentLRUCache<String, String>(
552 PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE);
553 private Set<String> _ignorePaths = new HashSet<String>();
554
555 }