001/*
002 *  Copyright 2018 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.core.ui.resources.css;
017
018import java.net.URI;
019import java.net.URISyntaxException;
020import java.util.regex.Matcher;
021import java.util.regex.Pattern;
022
023import org.apache.commons.io.FilenameUtils;
024
025/**
026 * Helper for CSS Files
027 */
028public final class CSSFileHelper
029{
030    // This regex matches 'src=' declarations
031    private static final Pattern CSS_URL_PATTERN_SRC = Pattern.compile("src" // the string 'src' literally
032                                                                     + "\\s*=\\s*" // the symbol equal, with any whitespace before or after
033                                                                     + "['\"](.*?)['\"]", // capture a string in between quotes, singles or double
034                                                                     Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
035    
036    // This regex matches any 'url()' that is not preceded by an @import directive
037    private static final Pattern CSS_URL_PATTERN_NOTIMPORT_URL = Pattern.compile("(?:@import\\s+url)" // The pattern '@import url' that we want to ignore. When matching this, group(1) will be null
038                                                                               + "|" // The right side of this "or" is only evaluated when we don't match the previous pattern
039                                                                               
040                                                                               + "(?:"
041                                                                               + "\\burl" // The word 'url'
042                                                                               + "\\s*"
043                                                                               + "\\(" // The opening parenthesis before the url
044                                                                               + "\\s*['\"]?(.*?)['\"]?\\s*" // Capture a string that could be surrounded by whitespace and quotes
045                                                                               + "\\)" // The closing parenthesis after the url
046                                                                               + ")", 
047                                                                               Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
048    
049    // This regex matches any 'url()' that is preceded by an @import directive
050    private static final Pattern CSS_URL_PATTERN_IMPORT_URL = Pattern.compile("@import\\s+url\\s*" // The literal '@import url' with any number of whitespace
051                                                                            + "\\(" // The opening parenthesis of the 'url()'
052                                                                            + "\\s*['\"]?(.*?)['\"]?\\s*" // Capture a string that could be surrounded by whitespace and quotes
053                                                                            + "\\)", // The closing parenthesis of the 'url()'
054                                                                            Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
055    
056    // This regex matches '@import' directives without 'url()'
057    private static final Pattern CSS_URL_PATTERN_IMPORT = Pattern.compile("@import\\s+" // The literal '@import' followed by whitespace
058                                                                        + "['\"](.*?)['\"]", // Capture a string that could be surrounded by quotes
059                                                                        Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
060    
061    private CSSFileHelper()
062    {
063    }
064    
065    /**
066     * Replace the relative URI inside a css file with the new context path
067     * @param content The file content
068     * @param fileUri The file Uri
069     * @param jsassResourceURIExtensionPoint The JSASS Resource URI extension point
070     * @param internalContextPath The internal context path of the application
071     * @param externalContextPath The external context path of the application
072     * @return The file content, with URIs replaced
073     * @throws URISyntaxException If an exception occurred
074     */
075    public static String replaceRelativeUri(String content, String fileUri, JSASSResourceURIExtensionPoint jsassResourceURIExtensionPoint, String internalContextPath, String externalContextPath) throws URISyntaxException
076    {
077        String c1 = _replaceRelativeUri(content, externalContextPath, fileUri, CSS_URL_PATTERN_SRC, jsassResourceURIExtensionPoint);
078        String c2 = _replaceRelativeUri(c1, externalContextPath, fileUri, CSS_URL_PATTERN_NOTIMPORT_URL, jsassResourceURIExtensionPoint);
079        String c3 = _replaceRelativeUri(c2, internalContextPath, fileUri, CSS_URL_PATTERN_IMPORT_URL, jsassResourceURIExtensionPoint);
080        String result = _replaceRelativeUri(c3, internalContextPath, fileUri, CSS_URL_PATTERN_IMPORT, jsassResourceURIExtensionPoint);
081
082        return result;
083    }
084    
085    /**
086     * Replace the relative URI inside a css file with the new context path
087     * @param content The file content
088     * @param fileUri The file Uri, without context path, for example "/plugins/pluginName/resources/style.css" or "/skins/skinName/resources/style.scss"
089     * @param jsassResourceURIExtensionPoint The JSASS Resource URI extension point
090     * @param externalContextPath The external context path of the application
091     * @return The file content, with URIs replaced
092     * @throws URISyntaxException If an exception occurred
093     */
094    public static String replaceRelativeResourcesUri(String content, String fileUri, JSASSResourceURIExtensionPoint jsassResourceURIExtensionPoint, String externalContextPath) throws URISyntaxException
095    {
096        String c1 = _replaceRelativeUri(content, externalContextPath, fileUri, CSS_URL_PATTERN_SRC, jsassResourceURIExtensionPoint);
097        String result = _replaceRelativeUri(c1, externalContextPath, fileUri, CSS_URL_PATTERN_NOTIMPORT_URL, jsassResourceURIExtensionPoint);
098
099        return result;
100    }
101    
102    private static String _replaceRelativeUri(String content, String contextPath, String fileUri, Pattern pattern, JSASSResourceURIExtensionPoint jsassResourceURIExtensionPoint)
103    {
104        Matcher urlMatcher = pattern.matcher(content);
105        StringBuffer sb = new StringBuffer();
106        
107        while (urlMatcher.find()) 
108        {
109            String fullMatch = urlMatcher.group();
110            String cssUrl = urlMatcher.group(1);
111            
112            if (cssUrl != null && !cssUrl.startsWith("data:"))
113            {
114                URI uri;
115                try
116                {
117                    uri = new URI(cssUrl.replaceAll("\\|", "%7C"));
118                    
119                    if (!uri.isAbsolute() && cssUrl.indexOf("/") != 0  && cssUrl.indexOf("#") != 0)
120                    {
121                        String fullFileUri = fileUri.indexOf('/') == 0 ? contextPath + fileUri : fileUri;
122                        String fullPathUrl = new URI(FilenameUtils.getFullPath(fullFileUri) + cssUrl).normalize().toString();
123                        urlMatcher.appendReplacement(sb, Matcher.quoteReplacement(fullMatch.replace(cssUrl, fullPathUrl)));
124                    }
125                    else
126                    {
127                        String relativeUri = jsassResourceURIExtensionPoint.resolve(cssUrl);
128                        if (relativeUri != null)
129                        {
130                            urlMatcher.appendReplacement(sb, Matcher.quoteReplacement(fullMatch.replace(cssUrl, relativeUri)));
131                        }
132                    }
133                }
134                catch (URISyntaxException e)
135                {
136                    urlMatcher.appendTail(sb);
137                    return sb.toString();
138                }
139            }
140        }
141        
142        urlMatcher.appendTail(sb);
143        
144        return sb.toString();
145    }
146
147}