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