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