| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.catalina.ssi; |
| |
| import java.io.IOException; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Locale; |
| import java.util.Set; |
| import java.util.TimeZone; |
| |
| import org.apache.catalina.util.RequestUtil; |
| import org.apache.catalina.util.Strftime; |
| import org.apache.catalina.util.URLEncoder; |
| |
| /** |
| * Allows the different SSICommand implementations to share data/talk to each |
| * other |
| * |
| * @author Bip Thelin |
| * @author Amy Roh |
| * @author Paul Speed |
| * @author Dan Sandberg |
| * @author David Becker |
| */ |
| public class SSIMediator { |
| protected static final String DEFAULT_CONFIG_ERR_MSG = "[an error occurred while processing this directive]"; |
| protected static final String DEFAULT_CONFIG_TIME_FMT = "%A, %d-%b-%Y %T %Z"; |
| protected static final String DEFAULT_CONFIG_SIZE_FMT = "abbrev"; |
| protected static final URLEncoder urlEncoder; |
| protected String configErrMsg = DEFAULT_CONFIG_ERR_MSG; |
| protected String configTimeFmt = DEFAULT_CONFIG_TIME_FMT; |
| protected String configSizeFmt = DEFAULT_CONFIG_SIZE_FMT; |
| protected final String className = getClass().getName(); |
| protected final SSIExternalResolver ssiExternalResolver; |
| protected final long lastModifiedDate; |
| protected Strftime strftime; |
| protected final SSIConditionalState conditionalState = new SSIConditionalState(); |
| static { |
| //We try to encode only the same characters that apache does |
| urlEncoder = new URLEncoder(); |
| urlEncoder.addSafeCharacter(','); |
| urlEncoder.addSafeCharacter(':'); |
| urlEncoder.addSafeCharacter('-'); |
| urlEncoder.addSafeCharacter('_'); |
| urlEncoder.addSafeCharacter('.'); |
| urlEncoder.addSafeCharacter('*'); |
| urlEncoder.addSafeCharacter('/'); |
| urlEncoder.addSafeCharacter('!'); |
| urlEncoder.addSafeCharacter('~'); |
| urlEncoder.addSafeCharacter('\''); |
| urlEncoder.addSafeCharacter('('); |
| urlEncoder.addSafeCharacter(')'); |
| } |
| |
| |
| public SSIMediator(SSIExternalResolver ssiExternalResolver, |
| long lastModifiedDate) { |
| this.ssiExternalResolver = ssiExternalResolver; |
| this.lastModifiedDate = lastModifiedDate; |
| setConfigTimeFmt(DEFAULT_CONFIG_TIME_FMT, true); |
| } |
| |
| |
| public void setConfigErrMsg(String configErrMsg) { |
| this.configErrMsg = configErrMsg; |
| } |
| |
| |
| public void setConfigTimeFmt(String configTimeFmt) { |
| setConfigTimeFmt(configTimeFmt, false); |
| } |
| |
| |
| public void setConfigTimeFmt(String configTimeFmt, boolean fromConstructor) { |
| this.configTimeFmt = configTimeFmt; |
| this.strftime = new Strftime(configTimeFmt, Locale.US); |
| //Variables like DATE_LOCAL, DATE_GMT, and LAST_MODIFIED need to be |
| // updated when |
| //the timefmt changes. This is what Apache SSI does. |
| setDateVariables(fromConstructor); |
| } |
| |
| |
| public void setConfigSizeFmt(String configSizeFmt) { |
| this.configSizeFmt = configSizeFmt; |
| } |
| |
| |
| public String getConfigErrMsg() { |
| return configErrMsg; |
| } |
| |
| |
| public String getConfigTimeFmt() { |
| return configTimeFmt; |
| } |
| |
| |
| public String getConfigSizeFmt() { |
| return configSizeFmt; |
| } |
| |
| |
| public SSIConditionalState getConditionalState() { |
| return conditionalState; |
| } |
| |
| |
| public Collection<String> getVariableNames() { |
| Set<String> variableNames = new HashSet<>(); |
| //These built-in variables are supplied by the mediator ( if not |
| // over-written by |
| // the user ) and always exist |
| variableNames.add("DATE_GMT"); |
| variableNames.add("DATE_LOCAL"); |
| variableNames.add("LAST_MODIFIED"); |
| ssiExternalResolver.addVariableNames(variableNames); |
| //Remove any variables that are reserved by this class |
| Iterator<String> iter = variableNames.iterator(); |
| while (iter.hasNext()) { |
| String name = iter.next(); |
| if (isNameReserved(name)) { |
| iter.remove(); |
| } |
| } |
| return variableNames; |
| } |
| |
| |
| public long getFileSize(String path, boolean virtual) throws IOException { |
| return ssiExternalResolver.getFileSize(path, virtual); |
| } |
| |
| |
| public long getFileLastModified(String path, boolean virtual) |
| throws IOException { |
| return ssiExternalResolver.getFileLastModified(path, virtual); |
| } |
| |
| |
| public String getFileText(String path, boolean virtual) throws IOException { |
| return ssiExternalResolver.getFileText(path, virtual); |
| } |
| |
| |
| protected boolean isNameReserved(String name) { |
| return name.startsWith(className + "."); |
| } |
| |
| |
| public String getVariableValue(String variableName) { |
| return getVariableValue(variableName, "none"); |
| } |
| |
| |
| public void setVariableValue(String variableName, String variableValue) { |
| if (!isNameReserved(variableName)) { |
| ssiExternalResolver.setVariableValue(variableName, variableValue); |
| } |
| } |
| |
| |
| public String getVariableValue(String variableName, String encoding) { |
| String lowerCaseVariableName = variableName.toLowerCase(Locale.ENGLISH); |
| String variableValue = null; |
| if (!isNameReserved(lowerCaseVariableName)) { |
| //Try getting it externally first, if it fails, try getting the |
| // 'built-in' |
| // value |
| variableValue = ssiExternalResolver.getVariableValue(variableName); |
| if (variableValue == null) { |
| variableName = variableName.toUpperCase(Locale.ENGLISH); |
| variableValue = ssiExternalResolver |
| .getVariableValue(className + "." + variableName); |
| } |
| if (variableValue != null) { |
| variableValue = encode(variableValue, encoding); |
| } |
| } |
| return variableValue; |
| } |
| |
| |
| /** |
| * Applies variable substitution to the specified String and returns the |
| * new resolved string. |
| * @param val The value which should be checked |
| * @return the value after variable substitution |
| */ |
| public String substituteVariables(String val) { |
| // If it has no references or HTML entities then no work |
| // need to be done |
| if (val.indexOf('$') < 0 && val.indexOf('&') < 0) return val; |
| |
| // HTML decoding |
| val = val.replace("<", "<"); |
| val = val.replace(">", ">"); |
| val = val.replace(""", "\""); |
| val = val.replace("&", "&"); |
| |
| StringBuilder sb = new StringBuilder(val); |
| int charStart = sb.indexOf("&#"); |
| while (charStart > -1) { |
| int charEnd = sb.indexOf(";", charStart); |
| if (charEnd > -1) { |
| char c = (char) Integer.parseInt( |
| sb.substring(charStart + 2, charEnd)); |
| sb.delete(charStart, charEnd + 1); |
| sb.insert(charStart, c); |
| charStart = sb.indexOf("&#"); |
| } else { |
| break; |
| } |
| } |
| |
| for (int i = 0; i < sb.length();) { |
| // Find the next $ |
| for (; i < sb.length(); i++) { |
| if (sb.charAt(i) == '$') { |
| i++; |
| break; |
| } |
| } |
| if (i == sb.length()) break; |
| // Check to see if the $ is escaped |
| if (i > 1 && sb.charAt(i - 2) == '\\') { |
| sb.deleteCharAt(i - 2); |
| i--; |
| continue; |
| } |
| int nameStart = i; |
| int start = i - 1; |
| int end = -1; |
| int nameEnd = -1; |
| char endChar = ' '; |
| // Check for {} wrapped var |
| if (sb.charAt(i) == '{') { |
| nameStart++; |
| endChar = '}'; |
| } |
| // Find the end of the var reference |
| for (; i < sb.length(); i++) { |
| if (sb.charAt(i) == endChar) break; |
| } |
| end = i; |
| nameEnd = end; |
| if (endChar == '}') end++; |
| // We should now have enough to extract the var name |
| String varName = sb.substring(nameStart, nameEnd); |
| String value = getVariableValue(varName); |
| if (value == null) value = ""; |
| // Replace the var name with its value |
| sb.replace(start, end, value); |
| // Start searching for the next $ after the value |
| // that was just substituted. |
| i = start + value.length(); |
| } |
| return sb.toString(); |
| } |
| |
| |
| protected String formatDate(Date date, TimeZone timeZone) { |
| String retVal; |
| if (timeZone != null) { |
| //we temporarily change strftime. Since SSIMediator is inherently |
| // single-threaded, this |
| //isn't a problem |
| TimeZone oldTimeZone = strftime.getTimeZone(); |
| strftime.setTimeZone(timeZone); |
| retVal = strftime.format(date); |
| strftime.setTimeZone(oldTimeZone); |
| } else { |
| retVal = strftime.format(date); |
| } |
| return retVal; |
| } |
| |
| |
| protected String encode(String value, String encoding) { |
| String retVal = null; |
| if (encoding.equalsIgnoreCase("url")) { |
| retVal = urlEncoder.encode(value, "UTF-8"); |
| } else if (encoding.equalsIgnoreCase("none")) { |
| retVal = value; |
| } else if (encoding.equalsIgnoreCase("entity")) { |
| retVal = RequestUtil.filter(value); |
| } else { |
| //This shouldn't be possible |
| throw new IllegalArgumentException("Unknown encoding: " + encoding); |
| } |
| return retVal; |
| } |
| |
| |
| public void log(String message) { |
| ssiExternalResolver.log(message, null); |
| } |
| |
| |
| public void log(String message, Throwable throwable) { |
| ssiExternalResolver.log(message, throwable); |
| } |
| |
| |
| protected void setDateVariables(boolean fromConstructor) { |
| boolean alreadySet = ssiExternalResolver.getVariableValue(className |
| + ".alreadyset") != null; |
| //skip this if we are being called from the constructor, and this has |
| // already |
| // been set |
| if (!(fromConstructor && alreadySet)) { |
| ssiExternalResolver.setVariableValue(className + ".alreadyset", |
| "true"); |
| Date date = new Date(); |
| TimeZone timeZone = TimeZone.getTimeZone("GMT"); |
| String retVal = formatDate(date, timeZone); |
| //If we are setting on of the date variables, we want to remove |
| // them from the |
| // user |
| //defined list of variables, because this is what Apache does |
| setVariableValue("DATE_GMT", null); |
| ssiExternalResolver.setVariableValue(className + ".DATE_GMT", |
| retVal); |
| retVal = formatDate(date, null); |
| setVariableValue("DATE_LOCAL", null); |
| ssiExternalResolver.setVariableValue(className + ".DATE_LOCAL", |
| retVal); |
| retVal = formatDate(new Date(lastModifiedDate), null); |
| setVariableValue("LAST_MODIFIED", null); |
| ssiExternalResolver.setVariableValue(className + ".LAST_MODIFIED", |
| retVal); |
| } |
| } |
| } |