001 package groovy.lang; 002 003 import groovy.security.GroovyCodeSourcePermission; 004 005 import java.io.ByteArrayInputStream; 006 import java.io.File; 007 import java.io.FileInputStream; 008 import java.io.FileNotFoundException; 009 import java.io.IOException; 010 import java.io.InputStream; 011 import java.net.MalformedURLException; 012 import java.net.URL; 013 import java.security.AccessController; 014 import java.security.CodeSource; 015 import java.security.PrivilegedActionException; 016 import java.security.PrivilegedExceptionAction; 017 import java.security.cert.Certificate; 018 019 /** 020 * CodeSource wrapper class that allows specific security policies to be associated with a class 021 * compiled from groovy source. 022 * 023 * @author Steve Goetze 024 */ 025 public class GroovyCodeSource { 026 027 /** 028 * The codeSource to be given the generated class. This can be used by policy file 029 * grants to administer security. 030 */ 031 private CodeSource codeSource; 032 /** The name given to the generated class */ 033 private String name; 034 /** The groovy source to be compiled and turned into a class */ 035 private InputStream inputStream; 036 /** The certificates used to sign the items from the codesource */ 037 Certificate[] certs; 038 private boolean cachable = false; 039 040 private File file; 041 042 public GroovyCodeSource(String script, String name, String codeBase) { 043 this(new ByteArrayInputStream(script.getBytes()), name, codeBase); 044 } 045 046 /** 047 * Construct a GroovyCodeSource for an inputStream of groovyCode that has an 048 * unknown provenance -- meaning it didn't come from a File or a URL (e.g. a String). 049 * The supplied codeBase will be used to construct a File URL that should match up 050 * with a java Policy entry that determines the grants to be associated with the 051 * class that will be built from the InputStream. 052 * 053 * The permission groovy.security.GroovyCodeSourcePermission will be used to determine if the given codeBase 054 * may be specified. That is, the current Policy set must have a GroovyCodeSourcePermission that implies 055 * the codeBase, or an exception will be thrown. This is to prevent callers from hijacking 056 * existing codeBase policy entries unless explicitly authorized by the user. 057 */ 058 public GroovyCodeSource(InputStream inputStream, String name, String codeBase) { 059 this.inputStream = inputStream; 060 this.name = name; 061 SecurityManager sm = System.getSecurityManager(); 062 if (sm != null) { 063 sm.checkPermission(new GroovyCodeSourcePermission(codeBase)); 064 } 065 try { 066 this.codeSource = new CodeSource(new URL("file", "", codeBase), (java.security.cert.Certificate[])null); 067 } catch (MalformedURLException murle) { 068 throw new RuntimeException("A CodeSource file URL cannot be constructed from the supplied codeBase: " + codeBase); 069 } 070 } 071 072 /** 073 * Package private constructor called by GroovyClassLoader for signed jar entries 074 */ 075 GroovyCodeSource(InputStream inputStream, String name, final File path, final Certificate[] certs) { 076 this.inputStream = inputStream; 077 this.name = name; 078 try { 079 this.codeSource = (CodeSource) AccessController.doPrivileged( new PrivilegedExceptionAction() { 080 public Object run() throws MalformedURLException { 081 //toURI().toURL() will encode, but toURL() will not. 082 return new CodeSource(path.toURI().toURL(), certs); 083 } 084 }); 085 } catch (PrivilegedActionException pae) { 086 //shouldn't happen 087 throw new RuntimeException("Could not construct a URL from: " + path); 088 } 089 } 090 091 public GroovyCodeSource(final File file) throws FileNotFoundException { 092 if (!file.exists()) 093 throw new FileNotFoundException(file.toString() + " (" + file.getAbsolutePath() + ")"); 094 else { 095 try { 096 if (!file.canRead()) 097 throw new RuntimeException(file.toString() + " can not be read. Check the read permisson of the file \"" + file.toString() + "\" (" + file.getAbsolutePath() + ")."); 098 } 099 catch (SecurityException e) { 100 throw e; 101 } 102 } 103 104 //this.inputStream = new FileInputStream(file); 105 this.file = file; 106 this.inputStream = null; 107 this.cachable = true; 108 //The calls below require access to user.dir - allow here since getName() and getCodeSource() are 109 //package private and used only by the GroovyClassLoader. 110 try { 111 Object[] info = (Object[]) AccessController.doPrivileged( new PrivilegedExceptionAction() { 112 public Object run() throws MalformedURLException { 113 Object[] info = new Object[2]; 114 URL url = file.toURI().toURL(); 115 info[0] = url.toExternalForm(); 116 //toURI().toURL() will encode, but toURL() will not. 117 info[1] = new CodeSource(url, (Certificate[]) null); 118 return info; 119 } 120 }); 121 this.name = (String) info[0]; 122 this.codeSource = (CodeSource) info[1]; 123 } catch (PrivilegedActionException pae) { 124 throw new RuntimeException("Could not construct a URL from: " + file); 125 } 126 } 127 128 public GroovyCodeSource(URL url) throws IOException { 129 if (url == null) { 130 throw new RuntimeException("Could not construct a GroovyCodeSource from a null URL"); 131 } 132 this.inputStream = url.openStream(); 133 this.name = url.toExternalForm(); 134 this.codeSource = new CodeSource(url, (java.security.cert.Certificate[])null); 135 } 136 137 CodeSource getCodeSource() { 138 return codeSource; 139 } 140 141 public InputStream getInputStream() { 142 try { 143 if (file!=null) return new FileInputStream(file); 144 } catch (FileNotFoundException fnfe) {} 145 return inputStream; 146 } 147 148 public String getName() { 149 return name; 150 } 151 152 public File getFile() { 153 return file; 154 } 155 156 public void setCachable(boolean b) { 157 cachable = b; 158 } 159 160 public boolean isCachable() { 161 return cachable; 162 } 163 }