001    /**
002     * Copyright (c) 2000-present 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.KMPSearch;
034    import com.liferay.portal.kernel.util.ParamUtil;
035    import com.liferay.portal.kernel.util.StringPool;
036    import com.liferay.portal.kernel.util.StringUtil;
037    import com.liferay.portal.kernel.util.Validator;
038    import com.liferay.portal.minifier.MinifierUtil;
039    import com.liferay.portal.servlet.filters.BasePortalFilter;
040    import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
041    import com.liferay.portal.util.PropsValues;
042    
043    import java.io.Writer;
044    
045    import java.nio.CharBuffer;
046    
047    import java.util.HashSet;
048    import java.util.Set;
049    import java.util.regex.Matcher;
050    import java.util.regex.Pattern;
051    
052    import javax.servlet.FilterChain;
053    import javax.servlet.FilterConfig;
054    import javax.servlet.ServletContext;
055    import javax.servlet.http.HttpServletRequest;
056    import javax.servlet.http.HttpServletResponse;
057    
058    /**
059     * @author Brian Wing Shun Chan
060     * @author Raymond Aug??
061     * @author Shuyang Zhou
062     */
063    public class StripFilter extends BasePortalFilter {
064    
065            public static final String SKIP_FILTER =
066                    StripFilter.class.getName() + "SKIP_FILTER";
067    
068            public StripFilter() {
069                    if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
070                            _minifierCache = new ConcurrentLFUCache<String, String>(
071                                    PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE);
072                    }
073            }
074    
075            @Override
076            public void init(FilterConfig filterConfig) {
077                    super.init(filterConfig);
078    
079                    for (String ignorePath : PropsValues.STRIP_IGNORE_PATHS) {
080                            _ignorePaths.add(ignorePath);
081                    }
082    
083                    _servletContext = filterConfig.getServletContext();
084            }
085    
086            @Override
087            public boolean isFilterEnabled(
088                    HttpServletRequest request, HttpServletResponse response) {
089    
090                    if (isStrip(request) && !isInclude(request) &&
091                            !isAlreadyFiltered(request)) {
092    
093                            return true;
094                    }
095                    else {
096                            return false;
097                    }
098            }
099    
100            protected String extractContent(CharBuffer charBuffer, int length) {
101    
102                    // See LPS-10545
103    
104                    /*String content = charBuffer.subSequence(0, length).toString();
105    
106                    int position = charBuffer.position();
107    
108                    charBuffer.position(position + length);*/
109    
110                    CharBuffer duplicateCharBuffer = charBuffer.duplicate();
111    
112                    int position = duplicateCharBuffer.position() + length;
113    
114                    String content = duplicateCharBuffer.limit(position).toString();
115    
116                    charBuffer.position(position);
117    
118                    return content;
119            }
120    
121            protected boolean hasLanguageAttribute(
122                    CharBuffer charBuffer, int startPos, int length) {
123    
124                    if (!PropsValues.STRIP_JS_LANGUAGE_ATTRIBUTE_SUPPORT_ENABLED) {
125                            return false;
126                    }
127    
128                    if (KMPSearch.search(
129                                    charBuffer, startPos, length, _MARKER_LANGUAGE,
130                                    _MARKER_LANGUAGE_NEXTS) == -1) {
131    
132                            return false;
133                    }
134    
135                    Matcher matcher = _javaScriptPattern.matcher(charBuffer);
136    
137                    if (matcher.find()) {
138                            return true;
139                    }
140    
141                    return false;
142            }
143    
144            protected boolean hasMarker(CharBuffer charBuffer, char[] marker) {
145                    int position = charBuffer.position();
146    
147                    if ((position + marker.length) >= charBuffer.limit()) {
148                            return false;
149                    }
150    
151                    for (int i = 0; i < marker.length; i++) {
152                            char c = marker[i];
153    
154                            char oldC = charBuffer.charAt(i);
155    
156                            if ((c != oldC) && (Character.toUpperCase(c) != oldC)) {
157                                    return false;
158                            }
159                    }
160    
161                    return true;
162            }
163    
164            protected boolean isAlreadyFiltered(HttpServletRequest request) {
165                    if (request.getAttribute(SKIP_FILTER) != null) {
166                            return true;
167                    }
168                    else {
169                            return false;
170                    }
171            }
172    
173            protected boolean isInclude(HttpServletRequest request) {
174                    String uri = (String)request.getAttribute(
175                            JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
176    
177                    if (uri == null) {
178                            return false;
179                    }
180                    else {
181                            return true;
182                    }
183            }
184    
185            protected boolean isStrip(HttpServletRequest request) {
186                    if (!ParamUtil.getBoolean(request, _STRIP, true)) {
187                            return false;
188                    }
189    
190                    String path = request.getPathInfo();
191    
192                    if (_ignorePaths.contains(path)) {
193                            if (_log.isDebugEnabled()) {
194                                    _log.debug("Ignore path " + path);
195                            }
196    
197                            return false;
198                    }
199    
200                    // Modifying binary content through a servlet filter under certain
201                    // conditions is bad on performance the user will not start downloading
202                    // the content until the entire content is modified.
203    
204                    String lifecycle = ParamUtil.getString(request, "p_p_lifecycle");
205    
206                    if ((lifecycle.equals("1") &&
207                             LiferayWindowState.isExclusive(request)) ||
208                            lifecycle.equals("2")) {
209    
210                            return false;
211                    }
212                    else {
213                            return true;
214                    }
215            }
216    
217            protected boolean isStripContentType(String contentType) {
218                    for (String stripContentType : PropsValues.STRIP_MIME_TYPES) {
219                            if (stripContentType.endsWith(StringPool.STAR)) {
220                                    stripContentType = stripContentType.substring(
221                                            0, stripContentType.length() - 1);
222    
223                                    if (contentType.startsWith(stripContentType)) {
224                                            return true;
225                                    }
226                            }
227                            else {
228                                    if (contentType.equals(stripContentType)) {
229                                            return true;
230                                    }
231                            }
232                    }
233    
234                    return false;
235            }
236    
237            protected void outputCloseTag(
238                            CharBuffer charBuffer, Writer writer, String closeTag)
239                    throws Exception {
240    
241                    writer.write(closeTag);
242    
243                    charBuffer.position(charBuffer.position() + closeTag.length());
244    
245                    skipWhiteSpace(charBuffer, writer, true);
246            }
247    
248            protected void outputOpenTag(
249                            CharBuffer charBuffer, Writer writer, char[] openTag)
250                    throws Exception {
251    
252                    writer.write(openTag);
253    
254                    charBuffer.position(charBuffer.position() + openTag.length);
255            }
256    
257            protected void processCSS(
258                            HttpServletRequest request, HttpServletResponse response,
259                            CharBuffer charBuffer, Writer writer)
260                    throws Exception {
261    
262                    outputOpenTag(charBuffer, writer, _MARKER_STYLE_OPEN);
263    
264                    int length = KMPSearch.search(
265                            charBuffer, _MARKER_STYLE_CLOSE, _MARKER_STYLE_CLOSE_NEXTS);
266    
267                    if (length == -1) {
268                            if (_log.isWarnEnabled()) {
269                                    _log.warn("Missing </style>");
270                            }
271    
272                            return;
273                    }
274    
275                    if (length == 0) {
276                            outputCloseTag(charBuffer, writer, _MARKER_STYLE_CLOSE);
277    
278                            return;
279                    }
280    
281                    String content = extractContent(charBuffer, length);
282    
283                    String minifiedContent = content;
284    
285                    if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
286                            CacheKeyGenerator cacheKeyGenerator =
287                                    CacheKeyGeneratorUtil.getCacheKeyGenerator(
288                                            StripFilter.class.getName());
289    
290                            String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
291    
292                            minifiedContent = _minifierCache.get(key);
293    
294                            if (minifiedContent == null) {
295                                    if (PropsValues.STRIP_CSS_SASS_ENABLED) {
296                                            try {
297                                                    content = DynamicCSSUtil.parseSass(
298                                                            _servletContext, request, request.getRequestURI(),
299                                                            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                            String resourceName, CharBuffer charBuffer, Writer writer,
434                            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                    writer.append(charBuffer, 0, endPos);
502    
503                    charBuffer.position(charBuffer.position() + endPos);
504    
505                    int length = KMPSearch.search(
506                            charBuffer, _MARKER_SCRIPT_CLOSE, _MARKER_SCRIPT_CLOSE_NEXTS);
507    
508                    if (length == -1) {
509                            if (_log.isWarnEnabled()) {
510                                    _log.warn("Missing </script>");
511                            }
512    
513                            return;
514                    }
515    
516                    if (length == 0) {
517                            outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
518    
519                            return;
520                    }
521    
522                    String content = extractContent(charBuffer, length);
523    
524                    String minifiedContent = content;
525    
526                    if (PropsValues.MINIFIER_INLINE_CONTENT_CACHE_SIZE > 0) {
527                            CacheKeyGenerator cacheKeyGenerator =
528                                    CacheKeyGeneratorUtil.getCacheKeyGenerator(
529                                            StripFilter.class.getName());
530    
531                            String key = String.valueOf(cacheKeyGenerator.getCacheKey(content));
532    
533                            minifiedContent = _minifierCache.get(key);
534    
535                            if (minifiedContent == null) {
536                                    minifiedContent = MinifierUtil.minifyJavaScript(
537                                            resourceName, content);
538    
539                                    boolean skipCache = false;
540    
541                                    for (String skipJavaScript :
542                                                    PropsValues.
543                                                            MINIFIER_INLINE_CONTENT_CACHE_SKIP_JAVASCRIPT) {
544    
545                                            if (minifiedContent.contains(skipJavaScript)) {
546                                                    skipCache = true;
547    
548                                                    break;
549                                            }
550                                    }
551    
552                                    if (!skipCache) {
553                                            _minifierCache.put(key, minifiedContent);
554                                    }
555                            }
556                    }
557    
558                    if (Validator.isNotNull(minifiedContent)) {
559                            writer.write(minifiedContent);
560                    }
561    
562                    outputCloseTag(charBuffer, writer, _MARKER_SCRIPT_CLOSE);
563            }
564    
565            protected void processPre(CharBuffer oldCharBuffer, Writer writer)
566                    throws Exception {
567    
568                    int length = KMPSearch.search(
569                            oldCharBuffer, _MARKER_PRE_OPEN.length + 1, _MARKER_PRE_CLOSE,
570                            _MARKER_PRE_CLOSE_NEXTS);
571    
572                    if (length == -1) {
573                            if (_log.isWarnEnabled()) {
574                                    _log.warn("Missing </pre>");
575                            }
576    
577                            outputOpenTag(oldCharBuffer, writer, _MARKER_PRE_OPEN);
578    
579                            return;
580                    }
581    
582                    length += _MARKER_PRE_CLOSE.length();
583    
584                    String content = extractContent(oldCharBuffer, length);
585    
586                    writer.write(content);
587    
588                    skipWhiteSpace(oldCharBuffer, writer, true);
589            }
590    
591            protected void processTextArea(CharBuffer oldCharBuffer, Writer writer)
592                    throws Exception {
593    
594                    int length = KMPSearch.search(
595                            oldCharBuffer, _MARKER_TEXTAREA_OPEN.length + 1,
596                            _MARKER_TEXTAREA_CLOSE, _MARKER_TEXTAREA_CLOSE_NEXTS);
597    
598                    if (length == -1) {
599                            if (_log.isWarnEnabled()) {
600                                    _log.warn("Missing </textArea>");
601                            }
602    
603                            outputOpenTag(oldCharBuffer, writer, _MARKER_TEXTAREA_OPEN);
604                            return;
605                    }
606    
607                    length += _MARKER_TEXTAREA_CLOSE.length();
608    
609                    String content = extractContent(oldCharBuffer, length);
610    
611                    writer.write(content);
612    
613                    skipWhiteSpace(oldCharBuffer, writer, true);
614            }
615    
616            protected boolean skipWhiteSpace(
617                            CharBuffer charBuffer, Writer writer, boolean appendSeparator)
618                    throws Exception {
619    
620                    boolean skipped = false;
621    
622                    for (int i = charBuffer.position(); i < charBuffer.limit(); i++) {
623                            char c = charBuffer.get();
624    
625                            if ((c == CharPool.SPACE) || (c == CharPool.TAB) ||
626                                    (c == CharPool.RETURN) || (c == CharPool.NEW_LINE)) {
627    
628                                    skipped = true;
629    
630                                    continue;
631                            }
632                            else {
633                                    charBuffer.position(i);
634    
635                                    break;
636                            }
637                    }
638    
639                    if (skipped && appendSeparator) {
640                            writer.write(CharPool.SPACE);
641                    }
642    
643                    return skipped;
644            }
645    
646            protected void strip(
647                            HttpServletRequest request, HttpServletResponse response,
648                            CharBuffer charBuffer, Writer writer)
649                    throws Exception {
650    
651                    skipWhiteSpace(charBuffer, writer, false);
652    
653                    while (charBuffer.hasRemaining()) {
654                            char c = charBuffer.get();
655    
656                            writer.write(c);
657    
658                            if (c == CharPool.LESS_THAN) {
659                                    if (hasMarker(charBuffer, _MARKER_INPUT_OPEN)) {
660                                            processInput(charBuffer, writer);
661    
662                                            continue;
663                                    }
664                                    else if (hasMarker(charBuffer, _MARKER_PRE_OPEN)) {
665                                            processPre(charBuffer, writer);
666    
667                                            continue;
668                                    }
669                                    else if (hasMarker(charBuffer, _MARKER_TEXTAREA_OPEN)) {
670                                            processTextArea(charBuffer, writer);
671    
672                                            continue;
673                                    }
674                                    else if (hasMarker(charBuffer, _MARKER_SCRIPT_OPEN)) {
675                                            StringBuffer requestURL = request.getRequestURL();
676    
677                                            processJavaScript(
678                                                    requestURL.toString(), charBuffer, writer,
679                                                    _MARKER_SCRIPT_OPEN);
680    
681                                            continue;
682                                    }
683                                    else if (hasMarker(charBuffer, _MARKER_STYLE_OPEN)) {
684                                            processCSS(request, response, charBuffer, writer);
685    
686                                            continue;
687                                    }
688                            }
689                            else if (c == CharPool.GREATER_THAN) {
690                                    skipWhiteSpace(charBuffer, writer, true);
691                            }
692    
693                            skipWhiteSpace(charBuffer, writer, true);
694                    }
695    
696                    writer.flush();
697            }
698    
699            private static final String _ENSURE_CONTENT_LENGTH = "ensureContentLength";
700    
701            private static final String _MARKER_INPUT_CLOSE = "/>";
702    
703            private static final int[] _MARKER_INPUT_CLOSE_NEXTS =
704                    KMPSearch.generateNexts(_MARKER_INPUT_CLOSE);
705    
706            private static final char[] _MARKER_INPUT_OPEN = "input".toCharArray();
707    
708            private static final String _MARKER_LANGUAGE = "language=";
709    
710            private static final int[] _MARKER_LANGUAGE_NEXTS = KMPSearch.generateNexts(
711                    _MARKER_LANGUAGE);
712    
713            private static final String _MARKER_PRE_CLOSE = "/pre>";
714    
715            private static final int[] _MARKER_PRE_CLOSE_NEXTS =
716                    KMPSearch.generateNexts(_MARKER_PRE_CLOSE);
717    
718            private static final char[] _MARKER_PRE_OPEN = "pre".toCharArray();
719    
720            private static final String _MARKER_SCRIPT_CLOSE = "</script>";
721    
722            private static final int[] _MARKER_SCRIPT_CLOSE_NEXTS =
723                    KMPSearch.generateNexts(_MARKER_SCRIPT_CLOSE);
724    
725            private static final char[] _MARKER_SCRIPT_OPEN = "script".toCharArray();
726    
727            private static final String _MARKER_STYLE_CLOSE = "</style>";
728    
729            private static final int[] _MARKER_STYLE_CLOSE_NEXTS =
730                    KMPSearch.generateNexts(_MARKER_STYLE_CLOSE);
731    
732            private static final char[] _MARKER_STYLE_OPEN =
733                    "style type=\"text/css\">".toCharArray();
734    
735            private static final String _MARKER_TEXTAREA_CLOSE = "/textarea>";
736    
737            private static final int[] _MARKER_TEXTAREA_CLOSE_NEXTS =
738                    KMPSearch.generateNexts(_MARKER_TEXTAREA_CLOSE);
739    
740            private static final char[] _MARKER_TEXTAREA_OPEN =
741                    "textarea ".toCharArray();
742    
743            private static final String _MARKER_TYPE_JAVASCRIPT =
744                    "type=\"text/javascript\"";
745    
746            private static final int[] _MARKER_TYPE_JAVASCRIPT_NEXTS =
747                    KMPSearch.generateNexts(_MARKER_TYPE_JAVASCRIPT);
748    
749            private static final String _STRIP = "strip";
750    
751            private static Log _log = LogFactoryUtil.getLog(StripFilter.class);
752    
753            private static Pattern _javaScriptPattern = Pattern.compile(
754                    "[Jj][aA][vV][aA][sS][cC][rR][iI][pP][tT]");
755    
756            private Set<String> _ignorePaths = new HashSet<String>();
757            private ConcurrentLFUCache<String, String> _minifierCache;
758            private ServletContext _servletContext;
759    
760    }