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