001    /**
002     * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.servlet.filters.strip;
016    
017    import com.liferay.portal.kernel.concurrent.ConcurrentLRUCache;
018    import com.liferay.portal.kernel.log.Log;
019    import com.liferay.portal.kernel.log.LogFactoryUtil;
020    import com.liferay.portal.kernel.portlet.LiferayWindowState;
021    import com.liferay.portal.kernel.servlet.StringServletResponse;
022    import com.liferay.portal.kernel.util.CharPool;
023    import com.liferay.portal.kernel.util.ContentTypes;
024    import com.liferay.portal.kernel.util.GetterUtil;
025    import com.liferay.portal.kernel.util.HttpUtil;
026    import com.liferay.portal.kernel.util.JavaConstants;
027    import com.liferay.portal.kernel.util.KMPSearch;
028    import com.liferay.portal.kernel.util.ParamUtil;
029    import com.liferay.portal.kernel.util.Validator;
030    import com.liferay.portal.servlet.filters.BasePortalFilter;
031    import com.liferay.portal.util.MinifierUtil;
032    import com.liferay.portal.util.PropsValues;
033    import com.liferay.util.servlet.ServletResponseUtil;
034    
035    import java.io.IOException;
036    import java.io.Writer;
037    
038    import java.nio.CharBuffer;
039    
040    import java.util.HashSet;
041    import java.util.Set;
042    
043    import javax.servlet.FilterChain;
044    import javax.servlet.FilterConfig;
045    import javax.servlet.http.HttpServletRequest;
046    import javax.servlet.http.HttpServletResponse;
047    
048    /**
049     * @author Brian Wing Shun Chan
050     * @author Raymond Augé
051     * @author Shuyang Zhou
052     */
053    public class StripFilter extends BasePortalFilter {
054    
055            public static final String SKIP_FILTER =
056                    StripFilter.class.getName() + "SKIP_FILTER";
057    
058            public void init(FilterConfig filterConfig) {
059                    super.init(filterConfig);
060    
061                    for (String ignorePath : PropsValues.STRIP_IGNORE_PATHS) {
062                            _ignorePaths.add(ignorePath);
063                    }
064            }
065    
066            protected String extractContent(CharBuffer charBuffer, int length) {
067    
068                    // See LPS-10545
069    
070                    /*String content = charBuffer.subSequence(0, length).toString();
071    
072                    int position = charBuffer.position();
073    
074                    charBuffer.position(position + length);*/
075    
076                    CharBuffer duplicateCharBuffer = charBuffer.duplicate();
077    
078                    int position = duplicateCharBuffer.position() + length;
079    
080                    String content = duplicateCharBuffer.limit(position).toString();
081    
082                    charBuffer.position(position);
083    
084                    return content;
085            }
086    
087            protected boolean hasMarker(CharBuffer charBuffer, char[] marker) {
088                    int position = charBuffer.position();
089    
090                    if ((position + marker.length) >= charBuffer.limit()) {
091                            return false;
092                    }
093    
094                    for (int i = 0; i < marker.length; i++) {
095                            char c = marker[i];
096    
097                            char oldC = charBuffer.charAt(i);
098    
099                            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                    // Modifying binary content through a servlet filter under certain
144                    // conditions is bad on performance the user will not start downloading
145                    // the content until the entire content is modified.
146    
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 void outputCloseTag(
161                            CharBuffer charBuffer, Writer writer, String closeTag)
162                    throws IOException {
163    
164                    writer.write(closeTag);
165    
166                    charBuffer.position(charBuffer.position() + closeTag.length());
167    
168                    skipWhiteSpace(charBuffer, writer);
169            }
170    
171            protected void outputOpenTag(
172                    CharBuffer charBuffer, Writer writer, char[] openTag)
173                    throws IOException {
174    
175                    writer.write(openTag);
176    
177                    charBuffer.position(charBuffer.position() + openTag.length);
178            }
179    
180            protected void processCSS(
181                            CharBuffer charBuffer, Writer writer)
182                    throws IOException {
183    
184                    outputOpenTag(charBuffer, writer, _MARKER_STYLE_OPEN);
185    
186                    int length = KMPSearch.search(
187                            charBuffer, _MARKER_STYLE_CLOSE, _MARKER_STYLE_CLOSE_NEXTS);
188    
189                    if (length == -1) {
190                            if (_log.isWarnEnabled()) {
191                                    _log.warn("Missing </style>");
192                            }
193    
194                            return;
195                    }
196    
197                    if (length == 0) {
198                            outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
199    
200                            return;
201                    }
202    
203                    String content = extractContent(charBuffer, length);
204    
205                    String minifiedContent = content;
206    
207                    if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
208                            String key = String.valueOf(content.hashCode());
209    
210                            minifiedContent = _minifierCache.get(key);
211    
212                            if (minifiedContent == null) {
213                                    minifiedContent = MinifierUtil.minifyCss(content);
214    
215                                    boolean skipCache = false;
216    
217                                    for (String skipCss :
218                                                    PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SKIP_CSS) {
219    
220                                            if (minifiedContent.contains(skipCss)) {
221                                                    skipCache = true;
222    
223                                                    break;
224                                            }
225                                    }
226    
227                                    if (!skipCache) {
228                                            _minifierCache.put(key, minifiedContent);
229                                    }
230                            }
231                    }
232    
233                    if (!Validator.isNull(minifiedContent)) {
234                            writer.write(minifiedContent);
235                    }
236    
237                    outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
238            }
239    
240            protected void processFilter(
241                            HttpServletRequest request, HttpServletResponse response,
242                            FilterChain filterChain)
243                    throws Exception {
244    
245                    if (isStrip(request) && !isInclude(request) &&
246                            !isAlreadyFiltered(request)) {
247    
248                            if (_log.isDebugEnabled()) {
249                                    String completeURL = HttpUtil.getCompleteURL(request);
250    
251                                    _log.debug("Stripping " + completeURL);
252                            }
253    
254                            request.setAttribute(SKIP_FILTER, Boolean.TRUE);
255    
256                            StringServletResponse stringResponse = new StringServletResponse(
257                                    response);
258    
259                            processFilter(
260                                    StripFilter.class, request, stringResponse, filterChain);
261    
262                            String contentType = GetterUtil.getString(
263                                    stringResponse.getContentType()).toLowerCase();
264    
265                            if (_log.isDebugEnabled()) {
266                                    _log.debug("Stripping content of type " + contentType);
267                            }
268    
269                            response.setContentType(contentType);
270    
271                            if (contentType.startsWith(ContentTypes.TEXT_HTML)) {
272                                    CharBuffer oldCharBuffer = CharBuffer.wrap(
273                                            stringResponse.getString());
274    
275                                    strip(oldCharBuffer, response.getWriter());
276                            }
277                            else {
278                                    ServletResponseUtil.write(response, stringResponse);
279                            }
280                    }
281                    else {
282                            if (_log.isDebugEnabled()) {
283                                    String completeURL = HttpUtil.getCompleteURL(request);
284    
285                                    _log.debug("Not stripping " + completeURL);
286                            }
287    
288                            processFilter(StripFilter.class, request, response, filterChain);
289                    }
290            }
291    
292            protected void processJavaScript(
293                            CharBuffer charBuffer, Writer writer, char[] openTag)
294                    throws IOException {
295    
296                    outputOpenTag(charBuffer, writer, openTag);
297    
298                    int length = KMPSearch.search(
299                            charBuffer, _MARKER_SCRIPT_CLOSE, _MARKER_SCRIPT_CLOSE_NEXTS);
300    
301                    if (length == -1) {
302                            if (_log.isWarnEnabled()) {
303                                    _log.warn("Missing </script>");
304                            }
305    
306                            return;
307                    }
308    
309                    if (length == 0) {
310                            outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
311    
312                            return;
313                    }
314    
315                    String content = extractContent(charBuffer, length);
316    
317                    String minifiedContent = content;
318    
319                    if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
320                            String key = String.valueOf(content.hashCode());
321    
322                            minifiedContent = _minifierCache.get(key);
323    
324                            if (minifiedContent == null) {
325                                    minifiedContent = MinifierUtil.minifyJavaScript(content);
326    
327                                    boolean skipCache = false;
328    
329                                    for (String skipJavaScript :
330                                                    PropsValues.
331                                                            MINIFIER_INLINE_CONTENT_CACHE_SKIP_JAVASCRIPT) {
332    
333                                            if (minifiedContent.contains(skipJavaScript)) {
334                                                    skipCache = true;
335    
336                                                    break;
337                                            }
338                                    }
339    
340                                    if (!skipCache) {
341                                            _minifierCache.put(key, minifiedContent);
342                                    }
343                            }
344                    }
345    
346                    if (!Validator.isNull(minifiedContent)) {
347                            writer.write(_CDATA_OPEN);
348                            writer.write(minifiedContent);
349                            writer.write(_CDATA_CLOSE);
350                    }
351    
352                    outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
353            }
354    
355            protected void processPre(CharBuffer oldCharBuffer, Writer writer)
356                    throws IOException {
357    
358                    int length = KMPSearch.search(
359                            oldCharBuffer, _MARKER_PRE_OPEN.length + 1, _MARKER_PRE_CLOSE,
360                            _MARKER_PRE_CLOSE_NEXTS);
361    
362                    if (length == -1) {
363                            if (_log.isWarnEnabled()) {
364                                    _log.warn("Missing </pre>");
365                            }
366    
367                            outputOpenTag(oldCharBuffer, writer, _MARKER_PRE_OPEN);
368    
369                            return;
370                    }
371    
372                    length += _MARKER_PRE_CLOSE.length();
373    
374                    String content = extractContent(oldCharBuffer, length);
375    
376                    writer.write(content);
377    
378                    skipWhiteSpace(oldCharBuffer, writer);
379            }
380    
381            protected void processTextArea(CharBuffer oldCharBuffer, Writer writer)
382                    throws IOException {
383    
384                    int length = KMPSearch.search(
385                            oldCharBuffer, _MARKER_TEXTAREA_OPEN.length + 1,
386                            _MARKER_TEXTAREA_CLOSE, _MARKER_TEXTAREA_CLOSE_NEXTS);
387    
388                    if (length == -1) {
389                            if (_log.isWarnEnabled()) {
390                                    _log.warn("Missing </textArea>");
391                            }
392    
393                            outputOpenTag(oldCharBuffer, writer, _MARKER_TEXTAREA_OPEN);
394                            return;
395                    }
396    
397                    length += _MARKER_TEXTAREA_CLOSE.length();
398    
399                    String content = extractContent(oldCharBuffer, length);
400    
401                    writer.write(content);
402    
403                    skipWhiteSpace(oldCharBuffer, writer);
404            }
405    
406            protected boolean skipWhiteSpace(CharBuffer charBuffer, Writer writer)
407                    throws IOException {
408    
409                    boolean skipped = false;
410    
411                    for (int i = charBuffer.position(); i < charBuffer.limit(); i++) {
412                            char c = charBuffer.get();
413    
414                            if ((c == CharPool.SPACE) || (c == CharPool.TAB) ||
415                                    (c == CharPool.RETURN) || (c == CharPool.NEW_LINE)) {
416    
417                                    skipped = true;
418    
419                                    continue;
420                            }
421                            else {
422                                    charBuffer.position(i);
423    
424                                    break;
425                            }
426                    }
427    
428                    if (skipped) {
429                            writer.write(CharPool.SPACE);
430                    }
431    
432                    return skipped;
433            }
434    
435            protected void strip(CharBuffer charBuffer, Writer writer)
436                    throws IOException {
437    
438                    skipWhiteSpace(charBuffer, writer);
439    
440                    while (charBuffer.hasRemaining()) {
441                            char c = charBuffer.get();
442    
443                            writer.write(c);
444    
445                            if (c == CharPool.LESS_THAN) {
446                                    if (hasMarker(charBuffer, _MARKER_PRE_OPEN)) {
447                                            processPre(charBuffer, writer);
448    
449                                            continue;
450                                    }
451                                    else if (hasMarker(charBuffer, _MARKER_TEXTAREA_OPEN)) {
452                                            processTextArea(charBuffer, writer);
453    
454                                            continue;
455                                    }
456                                    else if (hasMarker(charBuffer, _MARKER_JS_OPEN)) {
457                                            processJavaScript(charBuffer, writer, _MARKER_JS_OPEN);
458    
459                                            continue;
460                                    }
461                                    else if (hasMarker(charBuffer, _MARKER_SCRIPT_OPEN)) {
462                                            processJavaScript(charBuffer, writer, _MARKER_SCRIPT_OPEN);
463    
464                                            continue;
465                                    }
466                                    else if (hasMarker(charBuffer, _MARKER_STYLE_OPEN)) {
467                                            processCSS(charBuffer, writer);
468    
469                                            continue;
470                                    }
471                            }
472                            else if (c == CharPool.GREATER_THAN) {
473                                    skipWhiteSpace(charBuffer, writer);
474                            }
475                    }
476    
477                    writer.flush();
478            }
479    
480            private static final String _CDATA_CLOSE = "/*]]>*/";
481    
482            private static final String _CDATA_OPEN = "/*<![CDATA[*/";
483    
484            private static final char[] _MARKER_JS_OPEN =
485                    "script type=\"text/javascript\">".toCharArray();
486    
487            private static final String _MARKER_PRE_CLOSE = "/pre>";
488    
489            private static final int[] _MARKER_PRE_CLOSE_NEXTS =
490                    KMPSearch.generateNexts(_MARKER_PRE_CLOSE);
491    
492            private static final char[] _MARKER_PRE_OPEN = "pre>".toCharArray();
493    
494            private static final String _MARKER_SCRIPT_CLOSE = "</script>";
495    
496            private static final int[] _MARKER_SCRIPT_CLOSE_NEXTS =
497                    KMPSearch.generateNexts(_MARKER_SCRIPT_CLOSE);
498    
499            private static final char[] _MARKER_SCRIPT_OPEN = "script>".toCharArray();
500    
501            private static final String _MARKER_STYLE_CLOSE = "</style>";
502    
503            private static final int[] _MARKER_STYLE_CLOSE_NEXTS =
504                    KMPSearch.generateNexts(_MARKER_STYLE_CLOSE);
505    
506            private static final char[] _MARKER_STYLE_OPEN =
507                    "style type=\"text/css\">".toCharArray();
508    
509            private static final String _MARKER_TEXTAREA_CLOSE = "/textarea>";
510    
511            private static final int[] _MARKER_TEXTAREA_CLOSE_NEXTS =
512                    KMPSearch.generateNexts(_MARKER_TEXTAREA_CLOSE);
513    
514            private static final char[] _MARKER_TEXTAREA_OPEN =
515                    "textarea ".toCharArray();
516    
517            private static final String _STRIP = "strip";
518    
519            private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
520    
521            private ConcurrentLRUCache<String, String> _minifierCache =
522                    new ConcurrentLRUCache<String, String>(
523                            PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE);
524            private Set<String> _ignorePaths = new HashSet<String>();
525    
526    }