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