001    /**
002     * Copyright (c) 2000-2013 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.cache.key.CacheKeyGenerator;
018    import com.liferay.portal.kernel.cache.key.CacheKeyGeneratorUtil;
019    import com.liferay.portal.kernel.concurrent.ConcurrentLFUCache;
020    import com.liferay.portal.kernel.io.OutputStreamWriter;
021    import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
022    import com.liferay.portal.kernel.log.Log;
023    import com.liferay.portal.kernel.log.LogFactoryUtil;
024    import com.liferay.portal.kernel.portlet.LiferayWindowState;
025    import com.liferay.portal.kernel.scripting.ScriptingException;
026    import com.liferay.portal.kernel.servlet.BufferCacheServletResponse;
027    import com.liferay.portal.kernel.servlet.HttpHeaders;
028    import com.liferay.portal.kernel.servlet.ServletResponseUtil;
029    import com.liferay.portal.kernel.util.CharPool;
030    import com.liferay.portal.kernel.util.GetterUtil;
031    import com.liferay.portal.kernel.util.HttpUtil;
032    import com.liferay.portal.kernel.util.JavaConstants;
033    import com.liferay.portal.kernel.util.JavaDetector;
034    import com.liferay.portal.kernel.util.KMPSearch;
035    import com.liferay.portal.kernel.util.ParamUtil;
036    import com.liferay.portal.kernel.util.StringPool;
037    import com.liferay.portal.kernel.util.StringUtil;
038    import com.liferay.portal.kernel.util.Validator;
039    import com.liferay.portal.servlet.filters.BasePortalFilter;
040    import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
041    import com.liferay.portal.util.MinifierUtil;
042    import com.liferay.portal.util.PropsValues;
043    
044    import java.io.Writer;
045    
046    import java.nio.CharBuffer;
047    
048    import java.util.HashSet;
049    import java.util.Set;
050    import java.util.regex.Matcher;
051    import java.util.regex.Pattern;
052    
053    import javax.servlet.FilterChain;
054    import javax.servlet.FilterConfig;
055    import javax.servlet.ServletContext;
056    import javax.servlet.http.HttpServletRequest;
057    import javax.servlet.http.HttpServletResponse;
058    
059    /**
060     * @author Brian Wing Shun Chan
061     * @author Raymond Aug??
062     * @author Shuyang Zhou
063     */
064    public class StripFilter extends BasePortalFilter {
065    
066            public static final String SKIP_FILTER =
067                    StripFilter.class.getName() + "SKIP_FILTER";
068    
069            public StripFilter() {
070                    if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
071                            _minifierCache = new ConcurrentLFUCache<String, String>(
072                                    PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE);
073                    }
074            }
075    
076            @Override
077            public void init(FilterConfig filterConfig) {
078                    super.init(filterConfig);
079    
080                    for (String ignorePath : PropsValues.STRIP_IGNORE_PATHS) {
081                            _ignorePaths.add(ignorePath);
082                    }
083    
084                    _servletContext = filterConfig.getServletContext();
085            }
086    
087            @Override
088            public boolean isFilterEnabled(
089                    HttpServletRequest request, HttpServletResponse response) {
090    
091                    if (isStrip(request) && !isInclude(request) &&
092                            !isAlreadyFiltered(request)) {
093    
094                            return true;
095                    }
096                    else {
097                            return false;
098                    }
099            }
100    
101            protected String extractContent(CharBuffer charBuffer, int length) {
102    
103                    // See LPS-10545
104    
105                    /*String content = charBuffer.subSequence(0, length).toString();
106    
107                    int position = charBuffer.position();
108    
109                    charBuffer.position(position + length);*/
110    
111                    CharBuffer duplicateCharBuffer = charBuffer.duplicate();
112    
113                    int position = duplicateCharBuffer.position() + length;
114    
115                    String content = duplicateCharBuffer.limit(position).toString();
116    
117                    charBuffer.position(position);
118    
119                    return content;
120            }
121    
122            protected boolean hasLanguageAttribute(
123                    CharBuffer charBuffer, int startPos, int length) {
124    
125                    if (!PropsValues.STRIP_JS_LANGUAGE_ATTRIBUTE_SUPPORT_ENABLED) {
126                            return false;
127                    }
128    
129                    if (KMPSearch.search(
130                                    charBuffer, startPos, length, _MARKER_LANGUAGE,
131                                    _MARKER_LANGUAGE_NEXTS) == -1) {
132    
133                            return false;
134                    }
135    
136                    Matcher matcher = _javaScriptPattern.matcher(charBuffer);
137    
138                    if (matcher.find()) {
139                            return true;
140                    }
141    
142                    return false;
143            }
144    
145            protected boolean hasMarker(CharBuffer charBuffer, char[] marker) {
146                    int position = charBuffer.position();
147    
148                    if ((position + marker.length) >= charBuffer.limit()) {
149                            return false;
150                    }
151    
152                    for (int i = 0; i < marker.length; i++) {
153                            char c = marker[i];
154    
155                            char oldC = charBuffer.charAt(i);
156    
157                            if ((c != oldC) && (Character.toUpperCase(c) != oldC)) {
158                                    return false;
159                            }
160                    }
161    
162                    return true;
163            }
164    
165            protected boolean isAlreadyFiltered(HttpServletRequest request) {
166                    if (request.getAttribute(SKIP_FILTER) != null) {
167                            return true;
168                    }
169                    else {
170                            return false;
171                    }
172            }
173    
174            protected boolean isInclude(HttpServletRequest request) {
175                    String uri = (String)request.getAttribute(
176                            JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
177    
178                    if (uri == null) {
179                            return false;
180                    }
181                    else {
182                            return true;
183                    }
184            }
185    
186            protected boolean isStrip(HttpServletRequest request) {
187                    if (!ParamUtil.getBoolean(request, _STRIP, true)) {
188                            return false;
189                    }
190    
191                    String path = request.getPathInfo();
192    
193                    if (_ignorePaths.contains(path)) {
194                            if (_log.isDebugEnabled()) {
195                                    _log.debug("Ignore path " + path);
196                            }
197    
198                            return false;
199                    }
200    
201                    // Modifying binary content through a servlet filter under certain
202                    // conditions is bad on performance the user will not start downloading
203                    // the content until the entire content is modified.
204    
205                    String lifecycle = ParamUtil.getString(request, "p_p_lifecycle");
206    
207                    if ((lifecycle.equals("1") &&
208                             LiferayWindowState.isExclusive(request)) ||
209                            lifecycle.equals("2")) {
210    
211                            return false;
212                    }
213                    else {
214                            return true;
215                    }
216            }
217    
218            protected boolean isStripContentType(String contentType) {
219                    for (String stripContentType : PropsValues.STRIP_MIME_TYPES) {
220                            if (stripContentType.endsWith(StringPool.STAR)) {
221                                    stripContentType = stripContentType.substring(
222                                            0, stripContentType.length() - 1);
223    
224                                    if (contentType.startsWith(stripContentType)) {
225                                            return true;
226                                    }
227                            }
228                            else {
229                                    if (contentType.equals(stripContentType)) {
230                                            return true;
231                                    }
232                            }
233                    }
234    
235                    return false;
236            }
237    
238            protected void outputCloseTag(
239                            CharBuffer charBuffer, Writer writer, String closeTag)
240                    throws Exception {
241    
242                    writer.write(closeTag);
243    
244                    charBuffer.position(charBuffer.position() + closeTag.length());
245    
246                    skipWhiteSpace(charBuffer, writer, true);
247            }
248    
249            protected void outputOpenTag(
250                            CharBuffer charBuffer, Writer writer, char[] openTag)
251                    throws Exception {
252    
253                    writer.write(openTag);
254    
255                    charBuffer.position(charBuffer.position() + openTag.length);
256            }
257    
258            protected void processCSS(
259                            HttpServletRequest request, HttpServletResponse response,
260                            CharBuffer charBuffer, Writer writer)
261                    throws Exception {
262    
263                    outputOpenTag(charBuffer, writer, _MARKER_STYLE_OPEN);
264    
265                    int length = KMPSearch.search(
266                            charBuffer, _MARKER_STYLE_CLOSE, _MARKER_STYLE_CLOSE_NEXTS);
267    
268                    if (length == -1) {
269                            if (_log.isWarnEnabled()) {
270                                    _log.warn("Missing </style>");
271                            }
272    
273                            return;
274                    }
275    
276                    if (length == 0) {
277                            outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
278    
279                            return;
280                    }
281    
282                    String content = extractContent(charBuffer, length);
283    
284                    String minifiedContent = content;
285    
286                    if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
287                            CacheKeyGenerator cacheKeyGenerator =
288                                    CacheKeyGeneratorUtil.getCacheKeyGenerator(
289                                            StripFilter.class.getName());
290    
291                            String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
292    
293                            minifiedContent = _minifierCache.get(key);
294    
295                            if (minifiedContent == null) {
296                                    if (PropsValues.STRIP_CSS_SASS_ENABLED) {
297                                            try {
298                                                    content = DynamicCSSUtil.parseSass(
299                                                            _servletContext, request, request.getRequestURI(),
300                                                            content);
301                                            }
302                                            catch (ScriptingException se) {
303                                                    _log.error("Unable to parse SASS on CSS " + key, se);
304    
305                                                    if (_log.isDebugEnabled()) {
306                                                            _log.debug(content);
307                                                    }
308    
309                                                    if (response != null) {
310                                                            response.setHeader(
311                                                                    HttpHeaders.CACHE_CONTROL,
312                                                                    HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
313                                                    }
314                                            }
315                                    }
316    
317                                    minifiedContent = MinifierUtil.minifyCss(content);
318    
319                                    boolean skipCache = false;
320    
321                                    for (String skipCss :
322                                                    PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SKIP_CSS) {
323    
324                                            if (minifiedContent.contains(skipCss)) {
325                                                    skipCache = true;
326    
327                                                    break;
328                                            }
329                                    }
330    
331                                    if (!skipCache) {
332                                            _minifierCache.put(key, minifiedContent);
333                                    }
334                            }
335                    }
336    
337                    if (Validator.isNotNull(minifiedContent)) {
338                            writer.write(minifiedContent);
339                    }
340    
341                    outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
342            }
343    
344            @Override
345            protected void processFilter(
346                            HttpServletRequest request, HttpServletResponse response,
347                            FilterChain filterChain)
348                    throws Exception {
349    
350                    if (_log.isDebugEnabled()) {
351                            String completeURL = HttpUtil.getCompleteURL(request);
352    
353                            _log.debug("Stripping " + completeURL);
354                    }
355    
356                    request.setAttribute(SKIP_FILTER, Boolean.TRUE);
357    
358                    BufferCacheServletResponse bufferCacheServletResponse =
359                            new BufferCacheServletResponse(response);
360    
361                    processFilter(
362                            StripFilter.class, request, bufferCacheServletResponse,
363                            filterChain);
364    
365                    String contentType = GetterUtil.getString(
366                            bufferCacheServletResponse.getContentType());
367    
368                    contentType = StringUtil.toLowerCase(contentType);
369    
370                    if (_log.isDebugEnabled()) {
371                            _log.debug("Stripping content of type " + contentType);
372                    }
373    
374                    response.setContentType(contentType);
375    
376                    if (isStripContentType(contentType) &&
377                            (bufferCacheServletResponse.getStatus() ==
378                                    HttpServletResponse.SC_OK)) {
379    
380                            CharBuffer oldCharBuffer =
381                                    bufferCacheServletResponse.getCharBuffer();
382    
383                            boolean ensureContentLength = ParamUtil.getBoolean(
384                                    request, _ENSURE_CONTENT_LENGTH);
385    
386                            if (ensureContentLength) {
387                                    UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
388                                            new UnsyncByteArrayOutputStream();
389    
390                                    strip(
391                                            request, response, oldCharBuffer,
392                                            new OutputStreamWriter(unsyncByteArrayOutputStream));
393    
394                                    response.setContentLength(unsyncByteArrayOutputStream.size());
395    
396                                    unsyncByteArrayOutputStream.writeTo(response.getOutputStream());
397                            }
398                            else if (!response.isCommitted()) {
399                                    strip(request, response, oldCharBuffer, response.getWriter());
400                            }
401                    }
402                    else {
403                            ServletResponseUtil.write(response, bufferCacheServletResponse);
404                    }
405            }
406    
407            protected void processInput(CharBuffer oldCharBuffer, Writer writer)
408                    throws Exception {
409    
410                    int length = KMPSearch.search(
411                            oldCharBuffer, _MARKER_INPUT_OPEN.length + 1, _MARKER_INPUT_CLOSE,
412                            _MARKER_INPUT_CLOSE_NEXTS);
413    
414                    if (length == -1) {
415                            if (_log.isWarnEnabled()) {
416                                    _log.warn("Missing />");
417                            }
418    
419                            outputOpenTag(oldCharBuffer, writer, _MARKER_INPUT_OPEN);
420    
421                            return;
422                    }
423    
424                    length += _MARKER_INPUT_CLOSE.length();
425    
426                    String content = extractContent(oldCharBuffer, length);
427    
428                    writer.write(content);
429    
430                    skipWhiteSpace(oldCharBuffer, writer, true);
431            }
432    
433            protected void processJavaScript(
434                            CharBuffer charBuffer, Writer writer, char[] openTag)
435                    throws Exception {
436    
437                    int endPos = openTag.length + 1;
438    
439                    char c = charBuffer.charAt(openTag.length);
440    
441                    if (c == CharPool.SPACE) {
442                            int startPos = openTag.length + 1;
443    
444                            for (int i = startPos; i < charBuffer.length(); i++) {
445                                    c = charBuffer.charAt(i);
446    
447                                    if (c == CharPool.GREATER_THAN) {
448    
449                                            // Open script tag complete
450    
451                                            endPos = i + 1;
452    
453                                            int length = i - startPos;
454    
455                                            if ((length < _MARKER_TYPE_JAVASCRIPT.length()) ||
456                                                    (KMPSearch.search(
457                                                            charBuffer, startPos, length,
458                                                            _MARKER_TYPE_JAVASCRIPT,
459                                                            _MARKER_TYPE_JAVASCRIPT_NEXTS) == -1)) {
460    
461                                                    // We have just determined that this is an open script
462                                                    // tag that does not have the attribute
463                                                    // type="text/javascript". Now check to see if it has
464                                                    // the attribute language="JavaScript". If it does not,
465                                                    // then we skip stripping.
466    
467                                                    if (!hasLanguageAttribute(
468                                                                    charBuffer, startPos, length)) {
469    
470                                                            return;
471                                                    }
472                                            }
473    
474                                            // Open script tag has no attribute or has attribute
475                                            // type="text/javascript". Start stripping.
476    
477                                            break;
478                                    }
479                                    else if (c == CharPool.LESS_THAN) {
480    
481                                            // Illegal open script tag. Found a '<' before seeing a '>'.
482    
483                                            return;
484                                    }
485                            }
486    
487                            if (endPos == charBuffer.length()) {
488    
489                                    // Illegal open script tag. Unable to find a '>'.
490    
491                                    return;
492                            }
493                    }
494                    else if (c != CharPool.GREATER_THAN) {
495    
496                            // Illegal open script tag. Not followed by a '>' or a ' '.
497    
498                            return;
499                    }
500    
501                    if (JavaDetector.isJDK6()) {
502                            CharBuffer duplicateCharBuffer = charBuffer.duplicate();
503    
504                            int limit = duplicateCharBuffer.position() + endPos;
505    
506                            writer.append((CharSequence)duplicateCharBuffer.limit(limit));
507                    }
508                    else {
509                            writer.append(charBuffer, 0, endPos);
510                    }
511    
512                    charBuffer.position(charBuffer.position() + endPos);
513    
514                    int length = KMPSearch.search(
515                            charBuffer, _MARKER_SCRIPT_CLOSE, _MARKER_SCRIPT_CLOSE_NEXTS);
516    
517                    if (length == -1) {
518                            if (_log.isWarnEnabled()) {
519                                    _log.warn("Missing </script>");
520                            }
521    
522                            return;
523                    }
524    
525                    if (length == 0) {
526                            outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
527    
528                            return;
529                    }
530    
531                    String content = extractContent(charBuffer, length);
532    
533                    String minifiedContent = content;
534    
535                    if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
536                            CacheKeyGenerator cacheKeyGenerator =
537                                    CacheKeyGeneratorUtil.getCacheKeyGenerator(
538                                            StripFilter.class.getName());
539    
540                            String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
541    
542                            minifiedContent = _minifierCache.get(key);
543    
544                            if (minifiedContent == null) {
545                                    minifiedContent = MinifierUtil.minifyJavaScript(content);
546    
547                                    boolean skipCache = false;
548    
549                                    for (String skipJavaScript :
550                                                    PropsValues.
551                                                            MINIFIER_INLINE_CONTENT_CACHE_SKIP_JAVASCRIPT) {
552    
553                                            if (minifiedContent.contains(skipJavaScript)) {
554                                                    skipCache = true;
555    
556                                                    break;
557                                            }
558                                    }
559    
560                                    if (!skipCache) {
561                                            _minifierCache.put(key, minifiedContent);
562                                    }
563                            }
564                    }
565    
566                    if (Validator.isNotNull(minifiedContent)) {
567                            writer.write(minifiedContent);
568                    }
569    
570                    outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
571            }
572    
573            protected void processPre(CharBuffer oldCharBuffer, Writer writer)
574                    throws Exception {
575    
576                    int length = KMPSearch.search(
577                            oldCharBuffer, _MARKER_PRE_OPEN.length + 1, _MARKER_PRE_CLOSE,
578                            _MARKER_PRE_CLOSE_NEXTS);
579    
580                    if (length == -1) {
581                            if (_log.isWarnEnabled()) {
582                                    _log.warn("Missing </pre>");
583                            }
584    
585                            outputOpenTag(oldCharBuffer, writer, _MARKER_PRE_OPEN);
586    
587                            return;
588                    }
589    
590                    length += _MARKER_PRE_CLOSE.length();
591    
592                    String content = extractContent(oldCharBuffer, length);
593    
594                    writer.write(content);
595    
596                    skipWhiteSpace(oldCharBuffer, writer, true);
597            }
598    
599            protected void processTextArea(CharBuffer oldCharBuffer, Writer writer)
600                    throws Exception {
601    
602                    int length = KMPSearch.search(
603                            oldCharBuffer, _MARKER_TEXTAREA_OPEN.length + 1,
604                            _MARKER_TEXTAREA_CLOSE, _MARKER_TEXTAREA_CLOSE_NEXTS);
605    
606                    if (length == -1) {
607                            if (_log.isWarnEnabled()) {
608                                    _log.warn("Missing </textArea>");
609                            }
610    
611                            outputOpenTag(oldCharBuffer, writer, _MARKER_TEXTAREA_OPEN);
612                            return;
613                    }
614    
615                    length += _MARKER_TEXTAREA_CLOSE.length();
616    
617                    String content = extractContent(oldCharBuffer, length);
618    
619                    writer.write(content);
620    
621                    skipWhiteSpace(oldCharBuffer, writer, true);
622            }
623    
624            protected boolean skipWhiteSpace(
625                            CharBuffer charBuffer, Writer writer, boolean appendSeparator)
626                    throws Exception {
627    
628                    boolean skipped = false;
629    
630                    for (int i = charBuffer.position(); i < charBuffer.limit(); i++) {
631                            char c = charBuffer.get();
632    
633                            if ((c == CharPool.SPACE) || (c == CharPool.TAB) ||
634                                    (c == CharPool.RETURN) || (c == CharPool.NEW_LINE)) {
635    
636                                    skipped = true;
637    
638                                    continue;
639                            }
640                            else {
641                                    charBuffer.position(i);
642    
643                                    break;
644                            }
645                    }
646    
647                    if (skipped && appendSeparator) {
648                            writer.write(CharPool.SPACE);
649                    }
650    
651                    return skipped;
652            }
653    
654            protected void strip(
655                            HttpServletRequest request, HttpServletResponse response,
656                            CharBuffer charBuffer, Writer writer)
657                    throws Exception {
658    
659                    skipWhiteSpace(charBuffer, writer, false);
660    
661                    while (charBuffer.hasRemaining()) {
662                            char c = charBuffer.get();
663    
664                            writer.write(c);
665    
666                            if (c == CharPool.LESS_THAN) {
667                                    if (hasMarker(charBuffer, _MARKER_INPUT_OPEN)) {
668                                            processInput(charBuffer, writer);
669    
670                                            continue;
671                                    }
672                                    else if (hasMarker(charBuffer, _MARKER_PRE_OPEN)) {
673                                            processPre(charBuffer, writer);
674    
675                                            continue;
676                                    }
677                                    else if (hasMarker(charBuffer, _MARKER_TEXTAREA_OPEN)) {
678                                            processTextArea(charBuffer, writer);
679    
680                                            continue;
681                                    }
682                                    else if (hasMarker(charBuffer, _MARKER_SCRIPT_OPEN)) {
683                                            processJavaScript(charBuffer, writer, _MARKER_SCRIPT_OPEN);
684    
685                                            continue;
686                                    }
687                                    else if (hasMarker(charBuffer, _MARKER_STYLE_OPEN)) {
688                                            processCSS(request, response, charBuffer, writer);
689    
690                                            continue;
691                                    }
692                            }
693                            else if (c == CharPool.GREATER_THAN) {
694                                    skipWhiteSpace(charBuffer, writer, true);
695                            }
696    
697                            skipWhiteSpace(charBuffer, writer, true);
698                    }
699    
700                    writer.flush();
701            }
702    
703            private static final String _ENSURE_CONTENT_LENGTH = "ensureContentLength";
704    
705            private static final String _MARKER_INPUT_CLOSE = "/>";
706    
707            private static final int[] _MARKER_INPUT_CLOSE_NEXTS =
708                    KMPSearch.generateNexts(_MARKER_INPUT_CLOSE);
709    
710            private static final char[] _MARKER_INPUT_OPEN = "input".toCharArray();
711    
712            private static final String _MARKER_LANGUAGE = "language=";
713    
714            private static final int[] _MARKER_LANGUAGE_NEXTS = KMPSearch.generateNexts(
715                    _MARKER_LANGUAGE);
716    
717            private static final String _MARKER_PRE_CLOSE = "/pre>";
718    
719            private static final int[] _MARKER_PRE_CLOSE_NEXTS =
720                    KMPSearch.generateNexts(_MARKER_PRE_CLOSE);
721    
722            private static final char[] _MARKER_PRE_OPEN = "pre".toCharArray();
723    
724            private static final String _MARKER_SCRIPT_CLOSE = "</script>";
725    
726            private static final int[] _MARKER_SCRIPT_CLOSE_NEXTS =
727                    KMPSearch.generateNexts(_MARKER_SCRIPT_CLOSE);
728    
729            private static final char[] _MARKER_SCRIPT_OPEN = "script".toCharArray();
730    
731            private static final String _MARKER_STYLE_CLOSE = "</style>";
732    
733            private static final int[] _MARKER_STYLE_CLOSE_NEXTS =
734                    KMPSearch.generateNexts(_MARKER_STYLE_CLOSE);
735    
736            private static final char[] _MARKER_STYLE_OPEN =
737                    "style type=\"text/css\">".toCharArray();
738    
739            private static final String _MARKER_TEXTAREA_CLOSE = "/textarea>";
740    
741            private static final int[] _MARKER_TEXTAREA_CLOSE_NEXTS =
742                    KMPSearch.generateNexts(_MARKER_TEXTAREA_CLOSE);
743    
744            private static final char[] _MARKER_TEXTAREA_OPEN =
745                    "textarea ".toCharArray();
746    
747            private static final String _MARKER_TYPE_JAVASCRIPT =
748                    "type=\"text/javascript\"";
749    
750            private static final int[] _MARKER_TYPE_JAVASCRIPT_NEXTS =
751                    KMPSearch.generateNexts(_MARKER_TYPE_JAVASCRIPT);
752    
753            private static final String _STRIP = "strip";
754    
755            private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
756    
757            private static Pattern _javaScriptPattern = Pattern.compile(
758                    "[Jj][aA][vV][aA][sS][cC][rR][iI][pP][tT]");
759    
760            private Set<String> _ignorePaths = new HashSet<String>();
761            private ConcurrentLFUCache<String, String> _minifierCache;
762            private ServletContext _servletContext;
763    
764    }