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    }