/*
* 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 groovy.lang;
import groovy.security.
GroovyCodeSourcePermission;
import groovy.util.
CharsetToolkit;
import org.codehaus.groovy.runtime.
IOGroovyMethods;
import org.codehaus.groovy.runtime.
ResourceGroovyMethods;
import java.io.
File;
import java.io.
FileNotFoundException;
import java.io.
IOException;
import java.io.
Reader;
import java.net.
MalformedURLException;
import java.net.
URI;
import java.net.
URL;
import java.net.
URLConnection;
import java.security.
AccessController;
import java.security.
CodeSource;
import java.security.
PrivilegedActionException;
import java.security.
PrivilegedExceptionAction;
import java.security.cert.
Certificate;
import java.util.
Objects;
/**
* CodeSource wrapper class that allows specific security policies to be associated with a class
* compiled from groovy source.
*
* @author Steve Goetze
* @author Guillaume Laforge
* @author Merlyn Albery-Speyer
*/
public class
GroovyCodeSource {
/**
* The codeSource to be given the generated class. This can be used by policy file
* grants to administer security.
*/
private
CodeSource codeSource;
/**
* The name given to the generated class
*/
private
String name;
/**
* The groovy source to be compiled and turned into a class
*/
private
String scriptText;
/**
* The certificates used to sign the items from the codesource
*/
Certificate[]
certs;
private boolean
cachable;
private
File file;
private
URL url;
public
GroovyCodeSource(
String script,
String name,
String codeBase) {
this.
name =
name;
this.
scriptText =
script;
this.
codeSource =
createCodeSource(
codeBase);
this.
cachable = true;
}
/**
* Construct a GroovyCodeSource for an inputStream of groovyCode that has an
* unknown provenance -- meaning it didn't come from a File or a URL (e.g. a String).
* The supplied codeBase will be used to construct a File URL that should match up
* with a java Policy entry that determines the grants to be associated with the
* class that will be built from the InputStream.
* <p>
* The permission groovy.security.GroovyCodeSourcePermission will be used to determine if the given codeBase
* may be specified. That is, the current Policy set must have a GroovyCodeSourcePermission that implies
* the codeBase, or an exception will be thrown. This is to prevent callers from hijacking
* existing codeBase policy entries unless explicitly authorized by the user.
*/
public
GroovyCodeSource(
Reader reader,
String name,
String codeBase) {
this.
name =
name;
this.
codeSource =
createCodeSource(
codeBase);
try {
this.
scriptText =
IOGroovyMethods.
getText(
reader);
} catch (
IOException e) {
throw new
RuntimeException("Impossible to read the text content from that reader, for script: " +
name + " with codeBase: " +
codeBase,
e);
}
}
public
GroovyCodeSource(final
File infile, final
String encoding) throws
IOException {
// avoid files which confuse us like ones with .. in path
final
File file = new
File(
infile.
getCanonicalPath());
if (!
file.
exists()) {
throw new
FileNotFoundException(
file.
toString() + " (" +
file.
getAbsolutePath() + ")");
}
if (
file.
isDirectory()) {
throw new
IllegalArgumentException(
file.
toString() + " (" +
file.
getAbsolutePath() + ") is a directory not a Groovy source file.");
}
try {
if (!
file.
canRead())
throw new
RuntimeException(
file.
toString() + " can not be read. Check the read permission of the file \"" +
file.
toString() + "\" (" +
file.
getAbsolutePath() + ").");
}
catch (
SecurityException e) {
throw
e;
}
this.
file =
file;
this.
cachable = true;
//The calls below require access to user.dir - allow here since getName() and getCodeSource() are
//package private and used only by the GroovyClassLoader.
try {
Object[]
info =
AccessController.
doPrivileged(new
PrivilegedExceptionAction<
Object[]>() {
public
Object[]
run() throws
IOException {
// retrieve the content of the file using the provided encoding
if (
encoding != null) {
scriptText =
ResourceGroovyMethods.
getText(
infile,
encoding);
} else {
scriptText =
ResourceGroovyMethods.
getText(
infile);
}
Object[]
info = new
Object[2];
URL url =
file.
toURI().
toURL();
info[0] =
url.
toExternalForm();
//toURI().toURL() will encode, but toURL() will not.
info[1] = new
CodeSource(
url, (
Certificate[]) null);
return
info;
}
});
this.
name = (
String)
info[0];
this.
codeSource = (
CodeSource)
info[1];
} catch (
PrivilegedActionException pae) {
Throwable cause =
pae.
getCause();
if (
cause instanceof
IOException) {
throw (
IOException)
cause;
}
throw new
RuntimeException("Could not construct CodeSource for file: " +
file,
cause);
}
}
/**
* @param infile the file to create a GroovyCodeSource for.
* @throws IOException if an issue arises opening and reading the file.
*/
public
GroovyCodeSource(final
File infile) throws
IOException {
this(
infile,
CharsetToolkit.
getDefaultSystemCharset().
name());
}
public
GroovyCodeSource(
URI uri) throws
IOException {
this(
uri.
toURL());
}
public
GroovyCodeSource(
URL url) {
if (
url == null) {
throw new
RuntimeException("Could not construct a GroovyCodeSource from a null URL");
}
this.
url =
url;
// TODO: GROOVY-6561: GroovyMain got the name this way: script.substring(script.lastIndexOf("/") + 1)
this.
name =
url.
toExternalForm();
this.
codeSource = new
CodeSource(
url, (java.security.cert.
Certificate[]) null);
try {
String contentEncoding =
getContentEncoding(
url);
if (
contentEncoding != null) {
this.
scriptText =
ResourceGroovyMethods.
getText(
url,
contentEncoding);
} else {
this.
scriptText =
ResourceGroovyMethods.
getText(
url); // falls-back on default encoding
}
} catch (
IOException e) {
throw new
RuntimeException("Impossible to read the text content from " +
name,
e);
}
}
/**
* TODO(jwagenleitner): remove or fix in future release
*
* According to the spec getContentEncoding() returns the Content-Encoding
* HTTP Header which typically carries values such as 'gzip' or 'deflate'
* and is not the character set encoding. For compatibility in 2.4.x,
* this behavior is retained but should be removed or fixed (parse
* charset from Content-Type header) in future releases.
*
* see GROOVY-8056 and https://github.com/apache/groovy/pull/500
*/
private static
String getContentEncoding(
URL url) throws
IOException {
URLConnection urlConnection =
url.
openConnection();
String encoding =
urlConnection.
getContentEncoding();
try {
IOGroovyMethods.
closeQuietly(
urlConnection.
getInputStream());
} catch (
IOException ignore) {
// For compatibility, ignore exceptions from getInputStream() call
}
return
encoding;
}
public
CodeSource getCodeSource() {
return
codeSource;
}
public
String getScriptText() {
return
scriptText;
}
public
String getName() {
return
name;
}
public
File getFile() {
return
file;
}
public
URL getURL() {
return
url;
}
public void
setCachable(boolean
b) {
cachable =
b;
}
public boolean
isCachable() {
return
cachable;
}
private static
CodeSource createCodeSource(final
String codeBase) {
SecurityManager sm =
System.
getSecurityManager();
if (
sm != null) {
sm.
checkPermission(new
GroovyCodeSourcePermission(
codeBase));
}
try {
return new
CodeSource(new
URL("file", "",
codeBase), (java.security.cert.
Certificate[]) null);
}
catch (
MalformedURLException e) {
throw new
RuntimeException("A CodeSource file URL cannot be constructed from the supplied codeBase: " +
codeBase);
}
}
@
Override
public boolean
equals(
Object o) {
if (this ==
o) return true;
if (
o == null ||
getClass() !=
o.
getClass()) return false;
GroovyCodeSource that = (
GroovyCodeSource)
o;
return
Objects.
equals(
codeSource,
that.
codeSource);
}
@
Override
public int
hashCode() {
return
Objects.
hash(
codeSource);
}
}