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