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}