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