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, null, content);
300                                            }
301                                            catch (ScriptingException se) {
302                                                    _log.error("Unable to parse SASS on CSS " + key, se);
303    
304                                                    if (_log.isDebugEnabled()) {
305                                                            _log.debug(content);
306                                                    }
307    
308                                                    if (response != null) {
309                                                            response.setHeader(
310                                                                    HttpHeaders.CACHE_CONTROL,
311                                                                    HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
312                                                    }
313                                            }
314                                    }
315    
316                                    minifiedContent = MinifierUtil.minifyCss(content);
317    
318                                    boolean skipCache = false;
319    
320                                    for (String skipCss :
321                                                    PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SKIP_CSS) {
322    
323                                            if (minifiedContent.contains(skipCss)) {
324                                                    skipCache = true;
325    
326                                                    break;
327                                            }
328                                    }
329    
330                                    if (!skipCache) {
331                                            _minifierCache.put(key, minifiedContent);
332                                    }
333                            }
334                    }
335    
336                    if (Validator.isNotNull(minifiedContent)) {
337                            writer.write(minifiedContent);
338                    }
339    
340                    outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
341            }
342    
343            @Override
344            protected void processFilter(
345                            HttpServletRequest request, HttpServletResponse response,
346                            FilterChain filterChain)
347                    throws Exception {
348    
349                    if (_log.isDebugEnabled()) {
350                            String completeURL = HttpUtil.getCompleteURL(request);
351    
352                            _log.debug("Stripping " + completeURL);
353                    }
354    
355                    request.setAttribute(SKIP_FILTER, Boolean.TRUE);
356    
357                    BufferCacheServletResponse bufferCacheServletResponse =
358                            new BufferCacheServletResponse(response);
359    
360                    processFilter(
361                            StripFilter.class, request, bufferCacheServletResponse,
362                            filterChain);
363    
364                    String contentType = GetterUtil.getString(
365                            bufferCacheServletResponse.getContentType());
366    
367                    contentType = StringUtil.toLowerCase(contentType);
368    
369                    if (_log.isDebugEnabled()) {
370                            _log.debug("Stripping content of type " + contentType);
371                    }
372    
373                    response.setContentType(contentType);
374    
375                    if (isStripContentType(contentType) &&
376                            (bufferCacheServletResponse.getStatus() ==
377                                    HttpServletResponse.SC_OK)) {
378    
379                            CharBuffer oldCharBuffer =
380                                    bufferCacheServletResponse.getCharBuffer();
381    
382                            boolean ensureContentLength = ParamUtil.getBoolean(
383                                    request, _ENSURE_CONTENT_LENGTH);
384    
385                            if (ensureContentLength) {
386                                    UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
387                                            new UnsyncByteArrayOutputStream();
388    
389                                    strip(
390                                            request, response, oldCharBuffer,
391                                            new OutputStreamWriter(unsyncByteArrayOutputStream));
392    
393                                    response.setContentLength(unsyncByteArrayOutputStream.size());
394    
395                                    unsyncByteArrayOutputStream.writeTo(response.getOutputStream());
396                            }
397                            else if (!response.isCommitted()) {
398                                    strip(request, response, oldCharBuffer, response.getWriter());
399                            }
400                    }
401                    else {
402                            ServletResponseUtil.write(response, bufferCacheServletResponse);
403                    }
404            }
405    
406            protected void processInput(CharBuffer oldCharBuffer, Writer writer)
407                    throws Exception {
408    
409                    int length = KMPSearch.search(
410                            oldCharBuffer, _MARKER_INPUT_OPEN.length + 1, _MARKER_INPUT_CLOSE,
411                            _MARKER_INPUT_CLOSE_NEXTS);
412    
413                    if (length == -1) {
414                            if (_log.isWarnEnabled()) {
415                                    _log.warn("Missing />");
416                            }
417    
418                            outputOpenTag(oldCharBuffer, writer, _MARKER_INPUT_OPEN);
419    
420                            return;
421                    }
422    
423                    length += _MARKER_INPUT_CLOSE.length();
424    
425                    String content = extractContent(oldCharBuffer, length);
426    
427                    writer.write(content);
428    
429                    skipWhiteSpace(oldCharBuffer, writer, true);
430            }
431    
432            protected void processJavaScript(
433                            CharBuffer charBuffer, Writer writer, char[] openTag)
434                    throws Exception {
435    
436                    int endPos = openTag.length + 1;
437    
438                    char c = charBuffer.charAt(openTag.length);
439    
440                    if (c == CharPool.SPACE) {
441                            int startPos = openTag.length + 1;
442    
443                            for (int i = startPos; i < charBuffer.length(); i++) {
444                                    c = charBuffer.charAt(i);
445    
446                                    if (c == CharPool.GREATER_THAN) {
447    
448                                            // Open script tag complete
449    
450                                            endPos = i + 1;
451    
452                                            int length = i - startPos;
453    
454                                            if ((length < _MARKER_TYPE_JAVASCRIPT.length()) ||
455                                                    (KMPSearch.search(
456                                                            charBuffer, startPos, length,
457                                                            _MARKER_TYPE_JAVASCRIPT,
458                                                            _MARKER_TYPE_JAVASCRIPT_NEXTS) == -1)) {
459    
460                                                    // We have just determined that this is an open script
461                                                    // tag that does not have the attribute
462                                                    // type="text/javascript". Now check to see if it has
463                                                    // the attribute language="JavaScript". If it does not,
464                                                    // then we skip stripping.
465    
466                                                    if (!hasLanguageAttribute(
467                                                                    charBuffer, startPos, length)) {
468    
469                                                            return;
470                                                    }
471                                            }
472    
473                                            // Open script tag has no attribute or has attribute
474                                            // type="text/javascript". Start stripping.
475    
476                                            break;
477                                    }
478                                    else if (c == CharPool.LESS_THAN) {
479    
480                                            // Illegal open script tag. Found a '<' before seeing a '>'.
481    
482                                            return;
483                                    }
484                            }
485    
486                            if (endPos == charBuffer.length()) {
487    
488                                    // Illegal open script tag. Unable to find a '>'.
489    
490                                    return;
491                            }
492                    }
493                    else if (c != CharPool.GREATER_THAN) {
494    
495                            // Illegal open script tag. Not followed by a '>' or a ' '.
496    
497                            return;
498                    }
499    
500                    if (JavaDetector.isJDK6()) {
501                            CharBuffer duplicateCharBuffer = charBuffer.duplicate();
502    
503                            int limit = duplicateCharBuffer.position() + endPos;
504    
505                            writer.append((CharSequence)duplicateCharBuffer.limit(limit));
506                    }
507                    else {
508                            writer.append(charBuffer, 0, endPos);
509                    }
510    
511                    charBuffer.position(charBuffer.position() + endPos);
512    
513                    int length = KMPSearch.search(
514                            charBuffer, _MARKER_SCRIPT_CLOSE, _MARKER_SCRIPT_CLOSE_NEXTS);
515    
516                    if (length == -1) {
517                            if (_log.isWarnEnabled()) {
518                                    _log.warn("Missing </script>");
519                            }
520    
521                            return;
522                    }
523    
524                    if (length == 0) {
525                            outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
526    
527                            return;
528                    }
529    
530                    String content = extractContent(charBuffer, length);
531    
532                    String minifiedContent = content;
533    
534                    if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
535                            CacheKeyGenerator cacheKeyGenerator =
536                                    CacheKeyGeneratorUtil.getCacheKeyGenerator(
537                                            StripFilter.class.getName());
538    
539                            String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
540    
541                            minifiedContent = _minifierCache.get(key);
542    
543                            if (minifiedContent == null) {
544                                    minifiedContent = MinifierUtil.minifyJavaScript(content);
545    
546                                    boolean skipCache = false;
547    
548                                    for (String skipJavaScript :
549                                                    PropsValues.
550                                                            MINIFIER_INLINE_CONTENT_CACHE_SKIP_JAVASCRIPT) {
551    
552                                            if (minifiedContent.contains(skipJavaScript)) {
553                                                    skipCache = true;
554    
555                                                    break;
556                                            }
557                                    }
558    
559                                    if (!skipCache) {
560                                            _minifierCache.put(key, minifiedContent);
561                                    }
562                            }
563                    }
564    
565                    if (Validator.isNotNull(minifiedContent)) {
566                            writer.write(minifiedContent);
567                    }
568    
569                    outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
570            }
571    
572            protected void processPre(CharBuffer oldCharBuffer, Writer writer)
573                    throws Exception {
574    
575                    int length = KMPSearch.search(
576                            oldCharBuffer, _MARKER_PRE_OPEN.length + 1, _MARKER_PRE_CLOSE,
577                            _MARKER_PRE_CLOSE_NEXTS);
578    
579                    if (length == -1) {
580                            if (_log.isWarnEnabled()) {
581                                    _log.warn("Missing </pre>");
582                            }
583    
584                            outputOpenTag(oldCharBuffer, writer, _MARKER_PRE_OPEN);
585    
586                            return;
587                    }
588    
589                    length += _MARKER_PRE_CLOSE.length();
590    
591                    String content = extractContent(oldCharBuffer, length);
592    
593                    writer.write(content);
594    
595                    skipWhiteSpace(oldCharBuffer, writer, true);
596            }
597    
598            protected void processTextArea(CharBuffer oldCharBuffer, Writer writer)
599                    throws Exception {
600    
601                    int length = KMPSearch.search(
602                            oldCharBuffer, _MARKER_TEXTAREA_OPEN.length + 1,
603                            _MARKER_TEXTAREA_CLOSE, _MARKER_TEXTAREA_CLOSE_NEXTS);
604    
605                    if (length == -1) {
606                            if (_log.isWarnEnabled()) {
607                                    _log.warn("Missing </textArea>");
608                            }
609    
610                            outputOpenTag(oldCharBuffer, writer, _MARKER_TEXTAREA_OPEN);
611                            return;
612                    }
613    
614                    length += _MARKER_TEXTAREA_CLOSE.length();
615    
616                    String content = extractContent(oldCharBuffer, length);
617    
618                    writer.write(content);
619    
620                    skipWhiteSpace(oldCharBuffer, writer, true);
621            }
622    
623            protected boolean skipWhiteSpace(
624                            CharBuffer charBuffer, Writer writer, boolean appendSeparator)
625                    throws Exception {
626    
627                    boolean skipped = false;
628    
629                    for (int i = charBuffer.position(); i < charBuffer.limit(); i++) {
630                            char c = charBuffer.get();
631    
632                            if ((c == CharPool.SPACE) || (c == CharPool.TAB) ||
633                                    (c == CharPool.RETURN) || (c == CharPool.NEW_LINE)) {
634    
635                                    skipped = true;
636    
637                                    continue;
638                            }
639                            else {
640                                    charBuffer.position(i);
641    
642                                    break;
643                            }
644                    }
645    
646                    if (skipped && appendSeparator) {
647                            writer.write(CharPool.SPACE);
648                    }
649    
650                    return skipped;
651            }
652    
653            protected void strip(
654                            HttpServletRequest request, HttpServletResponse response,
655                            CharBuffer charBuffer, Writer writer)
656                    throws Exception {
657    
658                    skipWhiteSpace(charBuffer, writer, false);
659    
660                    while (charBuffer.hasRemaining()) {
661                            char c = charBuffer.get();
662    
663                            writer.write(c);
664    
665                            if (c == CharPool.LESS_THAN) {
666                                    if (hasMarker(charBuffer, _MARKER_INPUT_OPEN)) {
667                                            processInput(charBuffer, writer);
668    
669                                            continue;
670                                    }
671                                    else if (hasMarker(charBuffer, _MARKER_PRE_OPEN)) {
672                                            processPre(charBuffer, writer);
673    
674                                            continue;
675                                    }
676                                    else if (hasMarker(charBuffer, _MARKER_TEXTAREA_OPEN)) {
677                                            processTextArea(charBuffer, writer);
678    
679                                            continue;
680                                    }
681                                    else if (hasMarker(charBuffer, _MARKER_SCRIPT_OPEN)) {
682                                            processJavaScript(charBuffer, writer, _MARKER_SCRIPT_OPEN);
683    
684                                            continue;
685                                    }
686                                    else if (hasMarker(charBuffer, _MARKER_STYLE_OPEN)) {
687                                            processCSS(request, response, charBuffer, writer);
688    
689                                            continue;
690                                    }
691                            }
692                            else if (c == CharPool.GREATER_THAN) {
693                                    skipWhiteSpace(charBuffer, writer, true);
694                            }
695    
696                            skipWhiteSpace(charBuffer, writer, true);
697                    }
698    
699                    writer.flush();
700            }
701    
702            private static final String _ENSURE_CONTENT_LENGTH = "ensureContentLength";
703    
704            private static final String _MARKER_INPUT_CLOSE = "/>";
705    
706            private static final int[] _MARKER_INPUT_CLOSE_NEXTS =
707                    KMPSearch.generateNexts(_MARKER_INPUT_CLOSE);
708    
709            private static final char[] _MARKER_INPUT_OPEN = "input".toCharArray();
710    
711            private static final String _MARKER_LANGUAGE = "language=";
712    
713            private static final int[] _MARKER_LANGUAGE_NEXTS = KMPSearch.generateNexts(
714                    _MARKER_LANGUAGE);
715    
716            private static final String _MARKER_PRE_CLOSE = "/pre>";
717    
718            private static final int[] _MARKER_PRE_CLOSE_NEXTS =
719                    KMPSearch.generateNexts(_MARKER_PRE_CLOSE);
720    
721            private static final char[] _MARKER_PRE_OPEN = "pre".toCharArray();
722    
723            private static final String _MARKER_SCRIPT_CLOSE = "</script>";
724    
725            private static final int[] _MARKER_SCRIPT_CLOSE_NEXTS =
726                    KMPSearch.generateNexts(_MARKER_SCRIPT_CLOSE);
727    
728            private static final char[] _MARKER_SCRIPT_OPEN = "script".toCharArray();
729    
730            private static final String _MARKER_STYLE_CLOSE = "</style>";
731    
732            private static final int[] _MARKER_STYLE_CLOSE_NEXTS =
733                    KMPSearch.generateNexts(_MARKER_STYLE_CLOSE);
734    
735            private static final char[] _MARKER_STYLE_OPEN =
736                    "style type=\"text/css\">".toCharArray();
737    
738            private static final String _MARKER_TEXTAREA_CLOSE = "/textarea>";
739    
740            private static final int[] _MARKER_TEXTAREA_CLOSE_NEXTS =
741                    KMPSearch.generateNexts(_MARKER_TEXTAREA_CLOSE);
742    
743            private static final char[] _MARKER_TEXTAREA_OPEN =
744                    "textarea ".toCharArray();
745    
746            private static final String _MARKER_TYPE_JAVASCRIPT =
747                    "type=\"text/javascript\"";
748    
749            private static final int[] _MARKER_TYPE_JAVASCRIPT_NEXTS =
750                    KMPSearch.generateNexts(_MARKER_TYPE_JAVASCRIPT);
751    
752            private static final String _STRIP = "strip";
753    
754            private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
755    
756            private static Pattern _javaScriptPattern = Pattern.compile(
757                    "[Jj][aA][vV][aA][sS][cC][rR][iI][pP][tT]");
758    
759            private Set<String> _ignorePaths = new HashSet<String>();
760            private ConcurrentLFUCache<String, String> _minifierCache;
761            private ServletContext _servletContext;
762    
763    }