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