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