1 /* 2 * Copyright (c) 2003 The Visigoth Software Society. All rights 3 * reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in 14 * the documentation and/or other materials provided with the 15 * distribution. 16 * 17 * 3. The end-user documentation included with the redistribution, if 18 * any, must include the following acknowledgement: 19 * "This product includes software developed by the 20 * Visigoth Software Society (http://www.visigoths.org/)." 21 * Alternately, this acknowledgement may appear in the software itself, 22 * if and wherever such third-party acknowledgements normally appear. 23 * 24 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the 25 * project contributors may be used to endorse or promote products derived 26 * from this software without prior written permission. For written 27 * permission, please contact visigoths@visigoths.org. 28 * 29 * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth" 30 * nor may "FreeMarker" or "Visigoth" appear in their names 31 * without prior written permission of the Visigoth Software Society. 32 * 33 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 34 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 35 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 36 * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR 37 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 38 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 39 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 40 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 41 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 42 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 43 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 44 * SUCH DAMAGE. 45 * ==================================================================== 46 * 47 * This software consists of voluntary contributions made by many 48 * individuals on behalf of the Visigoth Software Society. For more 49 * information on the Visigoth Software Society, please see 50 * http://www.visigoths.org/ 51 */ 52 53 package freemarker.cache; 54 55 import java.io.File; 56 import java.io.FileInputStream; 57 import java.io.FileNotFoundException; 58 import java.io.IOException; 59 import java.io.InputStreamReader; 60 import java.io.Reader; 61 import java.security.AccessController; 62 import java.security.PrivilegedAction; 63 import java.security.PrivilegedActionException; 64 import java.security.PrivilegedExceptionAction; 65 66 import freemarker.template.utility.SecurityUtilities; 67 68 /** 69 * A {@link TemplateLoader} that uses files in a specified directory as the 70 * source of templates. If contains security checks that will prevent it 71 * serving templates outside the template directory (like <code><include /etc/passwd></code>. 72 * It compares canonical paths for this, so templates that are symbolically 73 * linked into the template directory from outside of it won't work either. 74 * @author Attila Szegedi, szegedia at freemail dot hu 75 * @version $Id: FileTemplateLoader.java,v 1.26 2004/03/29 08:06:22 szegedia Exp $ 76 */ 77 public class FileTemplateLoader implements TemplateLoader 78 { 79 private static final boolean SEP_IS_SLASH = File.separatorChar == '/'; 80 public final File baseDir; 81 private final String canonicalPath; 82 83 /** 84 * Creates a new file template cache that will use the current directory 85 * (the value of the system property <code>user.dir</code> as the base 86 * directory for loading templates. It will not allow access to template 87 * files that are accessible through symlinks that point outside the 88 * base directory. 89 */ 90 public FileTemplateLoader() 91 throws 92 IOException 93 { 94 this(new File(SecurityUtilities.getSystemProperty("user.dir"))); 95 } 96 97 /** 98 * Creates a new file template loader that will use the specified directory 99 * as the base directory for loading templates. It will not allow access to 100 * template files that are accessible through symlinks that point outside 101 * the base directory. 102 * @param baseDir the base directory for loading templates 103 */ 104 public FileTemplateLoader(final File baseDir) 105 throws 106 IOException 107 { 108 this(baseDir, false); 109 } 110 111 /** 112 * Creates a new file template loader that will use the specified directory 113 * as the base directory for loading templates. 114 * @param baseDir the base directory for loading templates 115 * @param allowLinking if true, it will allow 116 */ 117 public FileTemplateLoader(final File baseDir, final boolean allowLinking) 118 throws 119 IOException 120 { 121 try { 122 Object[] retval = (Object[]) AccessController.doPrivileged(new PrivilegedExceptionAction() { 123 public Object run() throws IOException { 124 if (!baseDir.exists()) { 125 throw new FileNotFoundException(baseDir + " does not exist."); 126 } 127 if (!baseDir.isDirectory()) { 128 throw new IOException(baseDir + " is not a directory."); 129 } 130 Object[] retval = new Object[2]; 131 if(allowLinking) { 132 retval[0] = baseDir; 133 retval[1] = null; 134 } 135 else { 136 retval[0] = baseDir.getCanonicalFile(); 137 retval[1] = ((File) retval[0]).getPath() + File.separatorChar; 138 } 139 return retval; 140 } 141 }); 142 this.baseDir = (File) retval[0]; 143 this.canonicalPath = (String) retval[1]; 144 } 145 catch(PrivilegedActionException e) 146 { 147 throw (IOException)e.getException(); 148 } 149 } 150 151 public Object findTemplateSource(final String name) 152 throws 153 IOException 154 { 155 try { 156 return AccessController.doPrivileged(new PrivilegedExceptionAction() { 157 public Object run() throws IOException { 158 File source = new File(baseDir, SEP_IS_SLASH ? name : 159 name.replace('/', File.separatorChar)); 160 if(!source.isFile()) { 161 return null; 162 } 163 // Security check for inadvertently returning something 164 // outside the template directory when linking is not 165 // allowed. 166 if(canonicalPath != null) { 167 String normalized = source.getCanonicalPath(); 168 if (!normalized.startsWith(canonicalPath)) { 169 throw new SecurityException(source.getAbsolutePath() 170 + " resolves to " + normalized + " which " + 171 " doesn't start with " + canonicalPath); 172 } 173 } 174 return source; 175 } 176 }); 177 } 178 catch(PrivilegedActionException e) 179 { 180 throw (IOException)e.getException(); 181 } 182 } 183 184 public long getLastModified(final Object templateSource) 185 { 186 return ((Long)(AccessController.doPrivileged(new PrivilegedAction() 187 { 188 public Object run() 189 { 190 return new Long(((File)templateSource).lastModified()); 191 } 192 }))).longValue(); 193 194 195 } 196 197 public Reader getReader(final Object templateSource, final String encoding) 198 throws 199 IOException 200 { 201 try 202 { 203 return (Reader)AccessController.doPrivileged(new PrivilegedExceptionAction() 204 { 205 public Object run() 206 throws 207 IOException 208 { 209 if (!(templateSource instanceof File)) { 210 throw new IllegalArgumentException( 211 "templateSource is a: " + 212 templateSource.getClass().getName()); 213 } 214 return new InputStreamReader(new FileInputStream( 215 (File)templateSource), encoding); 216 } 217 }); 218 } 219 catch(PrivilegedActionException e) 220 { 221 throw (IOException)e.getException(); 222 } 223 } 224 225 public void closeTemplateSource(Object templateSource) 226 { 227 // Do nothing. 228 } 229 }