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 }