package org.jruby.runtime.load;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URI;
import java.net.URL;
import java.security.AccessControlException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipException;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyFile;
import org.jruby.RubyHash;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyString;
import org.jruby.ast.executable.Script;
import org.jruby.exceptions.CatchThrow;
import org.jruby.exceptions.JumpException;
import org.jruby.exceptions.MainExitException;
import org.jruby.exceptions.RaiseException;
import org.jruby.exceptions.Unrescuable;
import org.jruby.ext.rbconfig.RbConfigLibrary;
import org.jruby.platform.Platform;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.FileResource;
import org.jruby.util.JRubyFile;
import org.jruby.util.collections.StringArraySet;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
import static org.jruby.runtime.Helpers.arrayOf;
import static org.jruby.util.URLUtil.getPath;
import org.jruby.util.cli.Options;
public class LoadService {
static final Logger LOG = LoggerFactory.getLogger(LoadService.class);
private final LoadTimer loadTimer;
private boolean canGetAbsolutePath = true;
public enum SuffixType {
Source, Extension, Both, Neither;
private static final String[] emptySuffixes = { "" };
public static final String[] sourceSuffixes =
Options.AOT_LOADCLASSES.load() ? arrayOf(".rb", ".class") : arrayOf(".rb");
public static final String[] extensionSuffixes = arrayOf(".jar");
private static final String[] allSuffixes;
static {
allSuffixes = new String[sourceSuffixes.length + extensionSuffixes.length];
System.arraycopy(sourceSuffixes, 0, allSuffixes, 0, sourceSuffixes.length);
System.arraycopy(extensionSuffixes, 0, allSuffixes, sourceSuffixes.length, extensionSuffixes.length);
}
public String[] getSuffixes() {
switch (this) {
case Source:
return sourceSuffixes;
case Extension:
return extensionSuffixes;
case Both:
return allSuffixes;
case Neither:
return emptySuffixes;
}
throw new RuntimeException("Unknown SuffixType: " + this);
}
}
protected static final Pattern sourcePattern = Pattern.compile("\\.(?:rb)$");
protected static final Pattern extensionPattern = Pattern.compile("\\.(?:so|o|dll|bundle|jar)$");
protected RubyArray loadPath;
protected StringArraySet loadedFeatures;
protected RubyArray loadedFeaturesDup;
private final Map<String, String> loadedFeaturesIndex = new ConcurrentHashMap<>(64);
protected final Map<String, JarFile> jarFiles = new HashMap<>();
protected final Ruby runtime;
protected final LibrarySearcher librarySearcher;
public LoadService(Ruby runtime) {
this.runtime = runtime;
if (RubyInstanceConfig.DEBUG_LOAD_TIMINGS) {
loadTimer = new TracingLoadTimer();
} else {
loadTimer = new LoadTimer();
}
this.librarySearcher = new LibrarySearcher(this);
}
public void init(List<String> prependDirectories) {
loadPath = RubyArray.newArray(runtime);
String jrubyHome = runtime.getJRubyHome();
loadedFeatures = new StringArraySet(runtime);
addPaths(prependDirectories);
RubyHash env = (RubyHash) runtime.getObject().getConstant("ENV");
RubyString env_rubylib = runtime.newString("RUBYLIB");
ThreadContext currentContext = runtime.getCurrentContext();
if (env.has_key_p(currentContext, env_rubylib).isTrue()) {
String rubylib = env.op_aref(currentContext, env_rubylib).toString();
String[] paths = rubylib.split(File.pathSeparator);
addPaths(paths);
}
try {
if (jrubyHome != null) {
addPath(RbConfigLibrary.getSiteDir(runtime));
if (!RbConfigLibrary.isSiteVendorSame(runtime)) {
addPath(RbConfigLibrary.getVendorDir(runtime));
}
String rubygemsDir = RbConfigLibrary.getRubygemsDir(runtime);
if (rubygemsDir != null) {
addPath(rubygemsDir);
}
addPath(RbConfigLibrary.getRubyLibDir(runtime));
}
} catch(SecurityException ignore) {}
addPaths(runtime.getInstanceConfig().getExtraLoadPaths());
}
public void addPaths(List<String> additionalDirectories) {
for (String dir : additionalDirectories) {
addPath(dir);
}
}
public void addPaths(String... additionalDirectories) {
for (String dir : additionalDirectories) {
addPath(dir);
}
}
public void provide(String shortName, String fullName) {
addLoadedFeature(shortName, fullName);
}
protected boolean isFeatureInIndex(String shortName) {
return loadedFeaturesIndex.containsKey(shortName);
}
@Deprecated
protected void addLoadedFeature(String name) {
addLoadedFeature(name, name);
}
protected void addLoadedFeature(String shortName, String name) {
loadedFeatures.appendString(runtime, name);
addFeatureToIndex(shortName, name);
}
protected void addFeatureToIndex(String shortName, String name) {
loadedFeaturesDup = (RubyArray)loadedFeatures.dup();
loadedFeaturesIndex.put(shortName, name);
}
protected void addPath(String path) {
if (path == null || path.length() == 0) return;
final RubyArray loadPath = this.loadPath;
synchronized(loadPath) {
final RubyString pathToAdd = runtime.newString(path.replace('\\', '/'));
if (loadPath.includes(runtime.getCurrentContext(), pathToAdd)) return;
loadPath.append(pathToAdd);
}
}
public void load(String file, boolean wrap) {
long startTime = loadTimer.startLoad(file);
int currentLine = runtime.getCurrentLine();
try {
if(!runtime.getProfile().allowLoad(file)) {
throw runtime.newLoadError("no such file to load -- " + file, file);
}
SearchState state = new SearchState(file);
state.prepareLoadSearch(file);
Library library = findLibraryBySearchState(state);
if (library == null) {
FileResource fileResource = JRubyFile.createResourceAsFile(runtime, file);
if (!fileResource.exists()) throw runtime.newLoadError("no such file to load -- " + file, file);
library = LibrarySearcher.ResourceLibrary.create(file, file, fileResource);
}
try {
library.load(runtime, wrap);
} catch (IOException e) {
debugLoadException(runtime, e);
throw newLoadErrorFromThrowable(runtime, file, e);
}
} finally {
runtime.setCurrentLine(currentLine);
loadTimer.endLoad(file, startTime);
}
}
public void loadFromClassLoader(ClassLoader classLoader, String file, boolean wrap) {
long startTime = loadTimer.startLoad("classloader:" + file);
int currentLine = runtime.getCurrentLine();
try {
SearchState state = new SearchState(file);
state.prepareLoadSearch(file);
Library library = null;
LoadServiceResource resource = getClassPathResource(classLoader, file);
if (resource != null) {
state.setLoadName(resolveLoadName(resource, file));
library = createLibrary(state, resource);
}
if (library == null) {
throw runtime.newLoadError("no such file to load -- " + file);
}
try {
library.load(runtime, wrap);
} catch (IOException e) {
debugLoadException(runtime, e);
throw newLoadErrorFromThrowable(runtime, file, e);
}
} finally {
runtime.setCurrentLine(currentLine);
loadTimer.endLoad("classloader:" + file, startTime);
}
}
public SearchState findFileForLoad(String file) {
if (Platform.IS_WINDOWS) {
file = file.replace('\\', '/');
}
if (file.endsWith(".so")) {
file = file.substring(0, file.length() - 3) + ".jar";
}
SearchState state = new SearchState(file);
state.prepareRequireSearch(file);
findLibraryBySearchState(state);
return state;
}
public boolean require(String requireName) {
return smartLoadInternal(requireName, true) == RequireState.LOADED;
}
public boolean autoloadRequire(String requireName) {
return smartLoadInternal(requireName, false) != RequireState.CIRCULAR;
}
private enum RequireState {
LOADED, ALREADY_LOADED, CIRCULAR
}
private final RequireLocks requireLocks = new RequireLocks();
enum LockResult { LOCKED, CIRCULAR }
private final class RequireLocks {
private final ConcurrentHashMap<String, ReentrantLock> pool;
private RequireLocks() {
this.pool = new ConcurrentHashMap<>(8, 0.75f, 2);
}
private LockResult lock(String requireName) {
ReentrantLock lock = pool.get(requireName);
if (lock == null) {
ReentrantLock newLock = new ReentrantLock();
lock = pool.putIfAbsent(requireName, newLock);
if (lock == null) lock = newLock;
}
if (lock.isHeldByCurrentThread()) return LockResult.CIRCULAR;
try {
runtime.getCurrentContext().getThread().enterSleep();
lock.lock();
} finally {
runtime.getCurrentContext().getThread().exitSleep();
}
return LockResult.LOCKED;
}
private void unlock(String requireName) {
ReentrantLock lock = pool.get(requireName);
if (lock != null) {
assert lock.isHeldByCurrentThread();
lock.unlock();
}
}
}
protected void warnCircularRequire(String requireName) {
StringBuilder sb = new StringBuilder("loading in progress, circular require considered harmful - " + requireName);
runtime.getCurrentContext().renderCurrentBacktrace(sb);
runtime.getWarnings().warn(sb.toString());
}
@Deprecated
public boolean smartLoad(String file) {
return require(file);
}
private RequireState smartLoadInternal(String file, boolean circularRequireWarning) {
checkEmptyLoad(file);
if (featureAlreadyLoaded(file)) {
return RequireState.ALREADY_LOADED;
}
SearchState state = findFileForLoad(file);
if (state.library == null) {
throw runtime.newLoadError("no such file to load -- " + state.searchFile, state.searchFile);
}
if (featureAlreadyLoaded(state.loadName)) {
return RequireState.ALREADY_LOADED;
}
if (!runtime.getProfile().allowRequire(file)) {
throw runtime.newLoadError("no such file to load -- " + file, file);
}
if (requireLocks.lock(state.loadName) == LockResult.CIRCULAR) {
if (circularRequireWarning && runtime.isVerbose()) {
warnCircularRequire(state.loadName);
}
return RequireState.CIRCULAR;
}
long startTime = loadTimer.startLoad(state.loadName);
try {
if (featureAlreadyLoaded(file)) {
return RequireState.ALREADY_LOADED;
}
if (featureAlreadyLoaded(state.loadName)) {
return RequireState.ALREADY_LOADED;
}
boolean loaded = tryLoadingLibraryOrScript(runtime, state);
if (loaded) {
addLoadedFeature(file, state.loadName);
}
return loaded ? RequireState.LOADED : RequireState.ALREADY_LOADED;
} finally {
loadTimer.endLoad(state.loadName, startTime);
requireLocks.unlock(state.loadName);
}
}
private static class LoadTimer {
public long startLoad(String file) { return 0L; }
public void endLoad(String file, long startTime) {}
}
private static final class TracingLoadTimer extends LoadTimer {
private final AtomicInteger indent = new AtomicInteger(0);
private StringBuilder getIndentString() {
final int i = indent.get();
StringBuilder buf = new StringBuilder(i * 2);
for (int j = 0; j < i; j++) {
buf.append(' ').append(' ');
}
return buf;
}
@Override
public long startLoad(String file) {
indent.incrementAndGet();
LOG.info( "{}-> {}", getIndentString(), file );
return System.currentTimeMillis();
}
@Override
public void endLoad(String file, long startTime) {
LOG.info( "{}<- {} - {}ms", getIndentString(), file, (System.currentTimeMillis() - startTime) );
indent.decrementAndGet();
}
}
public static void reflectedLoad(Ruby runtime, String libraryName, String className, ClassLoader classLoader, boolean wrap) {
try {
if (classLoader == null && Ruby.isSecurityRestricted()) {
classLoader = runtime.getInstanceConfig().getLoader();
}
Object libObject = classLoader.loadClass(className).newInstance();
if (libObject instanceof Library) {
Library library = (Library)libObject;
library.load(runtime, false);
} else if (libObject instanceof BasicLibraryService) {
BasicLibraryService service = (BasicLibraryService)libObject;
service.basicLoad(runtime);
} else {
throw runtime.newLoadError("library `" + libraryName + "' is not of type Library or BasicLibraryService", libraryName);
}
} catch (RaiseException re) {
throw re;
} catch (Throwable e) {
debugLoadException(runtime, e);
throw runtime.newLoadError("library `" + libraryName + "' could not be loaded: " + e, libraryName);
}
}
private static void debugLoadException(final Ruby runtime, final Throwable ex) {
if (runtime.isDebug()) ex.printStackTrace(runtime.getErr());
}
public IRubyObject getLoadPath() {
return loadPath;
}
public IRubyObject getLoadedFeatures() {
return loadedFeatures;
}
public void removeInternalLoadedFeature(String name) {
loadedFeatures.deleteString(runtime.getCurrentContext(), name);
}
private boolean isFeaturesIndexUpToDate() {
runtime.getCurrentContext().preTrace();
try {
return loadedFeaturesDup != null && loadedFeaturesDup.eql(loadedFeatures);
} finally {
runtime.getCurrentContext().postTrace();
}
}
public boolean featureAlreadyLoaded(String name) {
if (loadedFeatures.containsString(name)) return true;
if (!isFeaturesIndexUpToDate()) {
loadedFeaturesIndex.clear();
return false;
}
return isFeatureInIndex(name);
}
protected boolean isJarfileLibrary(SearchState state, final String file) {
return state.library instanceof JarredScript && file.endsWith(".jar");
}
protected void reraiseRaiseExceptions(Throwable e) throws RaiseException {
if (e instanceof RaiseException) {
throw (RaiseException) e;
}
}
@Deprecated
public interface LoadSearcher {
public boolean shouldTrySearch(SearchState state);
public boolean trySearch(SearchState state);
}
@Deprecated
public class BailoutSearcher implements LoadSearcher {
public boolean shouldTrySearch(SearchState state) {
return state.library == null;
}
protected boolean trySearch(String file, SuffixType suffixType) {
for (String suffix : suffixType.getSuffixes()) {
String searchName = file + suffix;
if (featureAlreadyLoaded(searchName)) {
return false;
}
}
return true;
}
public boolean trySearch(SearchState state) {
return trySearch(state.searchFile, state.suffixType);
}
}
@Deprecated
public class SourceBailoutSearcher extends BailoutSearcher {
public boolean shouldTrySearch(SearchState state) {
return !extensionPattern.matcher(state.loadName).find();
}
public boolean trySearch(SearchState state) {
return super.trySearch(state.searchFile, SuffixType.Source);
}
}
@Deprecated
public class NormalSearcher implements LoadSearcher {
public boolean shouldTrySearch(SearchState state) {
return state.library == null;
}
public boolean trySearch(SearchState state) {
state.library = findLibraryWithoutCWD(state, state.searchFile, state.suffixType);
return true;
}
}
@Deprecated
public class ClassLoaderSearcher implements LoadSearcher {
public boolean shouldTrySearch(SearchState state) {
return state.library == null;
}
public boolean trySearch(SearchState state) {
state.library = findLibraryWithClassloaders(state, state.searchFile, state.suffixType);
return true;
}
}
@Deprecated
public class ExtensionSearcher implements LoadSearcher {
public boolean shouldTrySearch(SearchState state) {
return (state.library == null || state.library instanceof JarredScript) && state.searchFile.length() > 0;
}
public boolean trySearch(SearchState state) {
debugLogTry("jarWithExtension", state.searchFile);
Library oldLibrary = state.library;
state.library = ClassExtensionLibrary.tryFind(runtime, state.searchFile);
debugLogFound("jarWithExtension", state.searchFile);
if(state.library == null && oldLibrary != null) {
state.library = oldLibrary;
}
return true;
}
}
@Deprecated
public class ScriptClassSearcher implements LoadSearcher {
public class ScriptClassLibrary implements Library {
private Script script;
public ScriptClassLibrary(Script script) {
this.script = script;
}
public void load(Ruby runtime, boolean wrap) {
runtime.loadScript(script, wrap);
}
}
public boolean shouldTrySearch(SearchState state) {
return state.library == null;
}
public boolean trySearch(SearchState state) throws RaiseException {
Script script;
String className = buildClassName(state.searchFile);
int lastSlashIndex = className.lastIndexOf('/');
if (lastSlashIndex > -1 && lastSlashIndex < className.length() - 1 && !Character.isJavaIdentifierStart(className.charAt(lastSlashIndex + 1))) {
if (lastSlashIndex == -1) {
className = '_' + className;
} else {
className = className.substring(0, lastSlashIndex + 1) + '_' + className.substring(lastSlashIndex + 1);
}
}
className = className.replace('/', '.');
try {
Class scriptClass = Class.forName(className);
script = (Script) scriptClass.newInstance();
} catch (Exception cnfe) {
return true;
}
state.library = new ScriptClassLibrary(script);
return true;
}
}
public static class SearchState {
public Library library;
public String loadName;
public SuffixType suffixType;
public String searchFile;
public SearchState(String file) {
loadName = file;
}
public void prepareRequireSearch(final String file) {
if (file.lastIndexOf('.') > file.lastIndexOf('/')) {
Matcher matcher;
if ((matcher = sourcePattern.matcher(file)).find()) {
suffixType = SuffixType.Source;
searchFile = file.substring(0, matcher.start());
} else if ((matcher = extensionPattern.matcher(file)).find()) {
suffixType = SuffixType.Extension;
searchFile = file.substring(0, matcher.start());
} else if (file.endsWith(".class")) {
suffixType = SuffixType.Neither;
searchFile = file;
} else {
suffixType = SuffixType.Both;
searchFile = file;
}
} else {
suffixType = SuffixType.Both;
searchFile = file;
}
}
public void prepareLoadSearch(final String file) {
if (file.lastIndexOf('.') > file.lastIndexOf('/')) {
Matcher matcher;
if ((matcher = sourcePattern.matcher(file)).find()) {
suffixType = SuffixType.Source;
searchFile = file.substring(0, matcher.start());
} else {
suffixType = SuffixType.Neither;
searchFile = file;
}
} else {
suffixType = SuffixType.Neither;
searchFile = file;
}
}
public void setLoadName(String loadName) {
this.loadName = loadName;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getName()).append(": ");
sb.append("library=").append(library.toString());
sb.append(", loadName=").append(loadName);
sb.append(", suffixType=").append(suffixType.toString());
sb.append(", searchFile=").append(searchFile);
return sb.toString();
}
}
protected boolean tryLoadingLibraryOrScript(Ruby runtime, SearchState state) {
try {
state.library.load(runtime, false);
return true;
}
catch (MainExitException ex) {
throw ex;
}
catch (RaiseException ex) {
if ( ex instanceof Unrescuable ) Helpers.throwException(ex);
if ( isJarfileLibrary(state, state.searchFile) ) return true;
throw ex;
}
catch (JumpException ex) {
throw ex;
}
catch (CatchThrow ex) {
throw ex;
}
catch (Throwable ex) {
if ( ex instanceof Unrescuable ) Helpers.throwException(ex);
if ( isJarfileLibrary(state, state.searchFile) ) return true;
debugLoadException(runtime, ex);
RaiseException re = newLoadErrorFromThrowable(runtime, state.searchFile, ex);
re.initCause(ex);
throw re;
}
}
private static RaiseException newLoadErrorFromThrowable(Ruby runtime, String file, Throwable t) {
if (RubyInstanceConfig.DEBUG_PARSER || RubyInstanceConfig.IR_READING_DEBUG) t.printStackTrace();
return runtime.newLoadError(String.format("load error: %s -- %s: %s", file, t.getClass().getName(), t.getMessage()), file);
}
@Deprecated
protected String buildClassName(String className) {
className = className.replaceFirst("^\\.\\/", "");
final int lastDot = className.lastIndexOf('.');
if (lastDot != -1) {
className = className.substring(0, lastDot);
}
className = className.replace("-", "_minus_").replace('.', '_');
return className;
}
protected void checkEmptyLoad(String file) throws RaiseException {
if (file.isEmpty()) {
throw runtime.newLoadError("no such file to load -- " + file, file);
}
}
@Deprecated
protected final void debugLogTry(String what, String msg) {
if (RubyInstanceConfig.DEBUG_LOAD_SERVICE) {
LOG.info( "trying {}: {}", what, msg );
}
}
@Deprecated
protected final void debugLogFound(String what, String msg) {
if (RubyInstanceConfig.DEBUG_LOAD_SERVICE) {
LOG.info( "found {}: {}", what, msg );
}
}
@Deprecated
protected final void debugLogFound( LoadServiceResource resource ) {
if (RubyInstanceConfig.DEBUG_LOAD_SERVICE) {
String resourceUrl;
try {
resourceUrl = resource.getURL().toString();
} catch (IOException e) {
resourceUrl = e.getMessage();
}
LOG.info( "found: {}", resourceUrl );
}
}
protected Library findLibraryBySearchState(SearchState state) {
if (librarySearcher.findBySearchState(state) != null) {
return state.library;
}
return null;
}
@Deprecated
protected Library findBuiltinLibrary(SearchState state, String baseName, SuffixType suffixType) {
for (String suffix : suffixType.getSuffixes()) {
String namePlusSuffix = baseName + suffix;
debugLogTry( "builtinLib", namePlusSuffix );
if (builtinLibraries.containsKey(namePlusSuffix)) {
state.setLoadName(namePlusSuffix);
Library lib = builtinLibraries.get(namePlusSuffix);
debugLogFound( "builtinLib", namePlusSuffix );
return lib;
}
}
return null;
}
@Deprecated
protected Library findLibraryWithoutCWD(SearchState state, String baseName, SuffixType suffixType) {
Library library = null;
switch (suffixType) {
case Both:
library = findBuiltinLibrary(state, baseName, SuffixType.Source);
if (library == null) library = createLibrary(state, tryResourceFromJarURL(state, baseName, SuffixType.Source));
if (library == null) library = createLibrary(state, tryResourceFromLoadPathOrURL(state, baseName, SuffixType.Source));
if (library == null) library = findBuiltinLibrary(state, baseName, SuffixType.Extension);
if (library == null) library = createLibrary(state, tryResourceFromJarURL(state, baseName, SuffixType.Extension));
if (library == null) library = createLibrary(state, tryResourceFromLoadPathOrURL(state, baseName, SuffixType.Extension));
break;
case Source:
case Extension:
library = findBuiltinLibrary(state, baseName, suffixType);
if (library == null) library = createLibrary(state, tryResourceFromJarURL(state, baseName, suffixType));
if (library == null) library = createLibrary(state, tryResourceFromLoadPathOrURL(state, baseName, suffixType));
break;
case Neither:
library = createLibrary(state, tryResourceFromJarURL(state, baseName, SuffixType.Neither));
if (library == null) library = createLibrary(state, tryResourceFromLoadPathOrURL(state, baseName, SuffixType.Neither));
break;
}
return library;
}
protected Library findLibraryWithClassloaders(SearchState state, String baseName, SuffixType suffixType) {
for (String suffix : suffixType.getSuffixes()) {
String file = baseName + suffix;
LoadServiceResource resource = findFileInClasspath(file);
if (resource != null) {
state.setLoadName(resolveLoadName(resource, file));
return createLibrary(state, resource);
}
}
return null;
}
@Deprecated
protected Library createLibrary(SearchState state, LoadServiceResource resource) {
if (resource == null) {
return null;
}
String file = resource.getName();
String location = state.loadName;
if (file.endsWith(".so") || file.endsWith(".dll") || file.endsWith(".bundle")) {
throw runtime.newLoadError("C extensions are disabled, can't load `" + resource.getName() + "'", resource.getName());
} else if (file.endsWith(".jar")) {
return new JarredScript(resource, state.searchFile);
} else if (file.endsWith(".class")) {
return new JavaCompiledScript(resource);
} else {
return new ExternalScript(resource, location);
}
}
@Deprecated
protected LoadServiceResource tryResourceFromCWD(SearchState state, String baseName,SuffixType suffixType) throws RaiseException {
LoadServiceResource foundResource = null;
for (String suffix : suffixType.getSuffixes()) {
String namePlusSuffix = baseName + suffix;
try {
JRubyFile file = JRubyFile.create(runtime.getCurrentDirectory(), RubyFile.expandUserPath(runtime.getCurrentContext(), namePlusSuffix));
debugLogTry("resourceFromCWD", file.toString());
if (file.isFile() && file.isAbsolute() && file.canRead()) {
boolean absolute = true;
foundResource = new LoadServiceResource(file, getFileName(file, namePlusSuffix), absolute);
debugLogFound(foundResource);
state.setLoadName(resolveLoadName(foundResource, namePlusSuffix));
break;
}
} catch (IllegalArgumentException illArgEx) {
} catch (SecurityException secEx) {
}
}
return foundResource;
}
@Deprecated
protected LoadServiceResource tryResourceFromDotSlash(SearchState state, String baseName, SuffixType suffixType) throws RaiseException {
LoadServiceResource foundResource = null;
for (String suffix : suffixType.getSuffixes()) {
String namePlusSuffix = baseName + suffix;
foundResource = tryResourceAsIs(namePlusSuffix, "resourceFromDotSlash");
if (foundResource != null) break;
}
return foundResource;
}
@Deprecated
protected LoadServiceResource tryResourceFromHome(SearchState state, String baseName, SuffixType suffixType) throws RaiseException {
LoadServiceResource foundResource = null;
RubyHash env = (RubyHash) runtime.getObject().getConstant("ENV");
RubyString env_home = runtime.newString("HOME");
if (env.has_key_p(env_home).isFalse()) {
return null;
}
String home = env.op_aref(runtime.getCurrentContext(), env_home).toString();
String path = baseName.substring(2);
for (String suffix : suffixType.getSuffixes()) {
String namePlusSuffix = path + suffix;
try {
JRubyFile file = JRubyFile.create(home, RubyFile.expandUserPath(runtime.getCurrentContext(), namePlusSuffix));
debugLogTry("resourceFromHome", file.toString());
if (file.isFile() && file.isAbsolute() && file.canRead()) {
boolean absolute = true;
state.setLoadName(file.getPath());
foundResource = new LoadServiceResource(file, state.loadName, absolute);
debugLogFound(foundResource);
break;
}
} catch (IllegalArgumentException illArgEx) {
} catch (SecurityException secEx) {
}
}
return foundResource;
}
@Deprecated
protected LoadServiceResource tryResourceFromJarURL(SearchState state, String baseName, SuffixType suffixType) {
LoadServiceResource foundResource = null;
if (baseName.startsWith("jar:file:")) {
return tryResourceFromJarURL(state, baseName.replaceFirst("jar:", ""), suffixType);
} else if (baseName.startsWith("jar:")) {
for (String suffix : suffixType.getSuffixes()) {
String namePlusSuffix = baseName + suffix;
try {
URI resourceUri = new URI("jar", namePlusSuffix.substring(4), null);
URL url = resourceUri.toURL();
debugLogTry("resourceFromJarURL", url.toString());
if (url.openStream() != null) {
foundResource = new LoadServiceResource(url, namePlusSuffix);
debugLogFound(foundResource);
}
} catch (FileNotFoundException e) {
} catch (URISyntaxException e) {
throw runtime.newIOError(e.getMessage());
} catch (MalformedURLException e) {
throw runtime.newIOErrorFromException(e);
} catch (IOException e) {
throw runtime.newIOErrorFromException(e);
}
if (foundResource != null) {
state.setLoadName(resolveLoadName(foundResource, namePlusSuffix));
break;
}
}
} else if(baseName.startsWith("file:") && baseName.indexOf("!/") != -1) {
for (String suffix : suffixType.getSuffixes()) {
String namePlusSuffix = baseName + suffix;
try {
String jarFile = namePlusSuffix.substring(5, namePlusSuffix.indexOf("!/"));
JarFile file = new JarFile(jarFile);
String expandedFilename = expandRelativeJarPath(namePlusSuffix.substring(namePlusSuffix.indexOf("!/") + 2));
debugLogTry("resourceFromJarURL", expandedFilename);
if(file.getJarEntry(expandedFilename) != null) {
URI resourceUri = new URI("jar", "file:" + jarFile + "!/" + expandedFilename, null);
foundResource = new LoadServiceResource(resourceUri.toURL(), namePlusSuffix);
debugLogFound(foundResource);
}
} catch (URISyntaxException e) {
throw runtime.newIOError(e.getMessage());
} catch (MalformedURLException e) {
throw runtime.newIOErrorFromException(e);
} catch(Exception e) {}
if (foundResource != null) {
state.setLoadName(resolveLoadName(foundResource, namePlusSuffix));
break;
}
}
}
return foundResource;
}
@Deprecated
protected LoadServiceResource tryResourceFromLoadPathOrURL(SearchState state, String baseName, SuffixType suffixType) {
LoadServiceResource foundResource = null;
if (baseName.startsWith("./")) {
foundResource = tryResourceFromDotSlash(state, baseName, suffixType);
if (foundResource != null) {
state.setLoadName(resolveLoadName(foundResource, foundResource.getName()));
}
return foundResource;
}
if (baseName.startsWith("~/")) {
foundResource = tryResourceFromHome(state, baseName, suffixType);
if (foundResource != null) {
state.setLoadName(resolveLoadName(foundResource, foundResource.getName()));
}
return foundResource;
}
if (new File(baseName).isAbsolute() || baseName.startsWith("../")) {
for (String suffix : suffixType.getSuffixes()) {
String namePlusSuffix = baseName + suffix;
foundResource = tryResourceAsIs(namePlusSuffix);
if (foundResource != null) {
state.setLoadName(resolveLoadName(foundResource, namePlusSuffix));
return foundResource;
}
}
return null;
}
Outer: for (int i = 0; i < loadPath.size(); i++) {
String loadPathEntry = getLoadPathEntry(loadPath.eltInternal(i));
if (loadPathEntry.equals(".") || loadPathEntry.equals("")) {
foundResource = tryResourceFromCWD(state, baseName, suffixType);
if (foundResource != null) {
String ss = foundResource.getName();
if(ss.startsWith("./")) {
ss = ss.substring(2);
}
state.setLoadName(resolveLoadName(foundResource, ss));
break Outer;
}
} else {
boolean looksLikeJarURL = loadPathLooksLikeJarURL(loadPathEntry);
boolean looksLikeClasspathURL = loadPathLooksLikeClasspathURL(loadPathEntry);
for (String suffix : suffixType.getSuffixes()) {
String namePlusSuffix = baseName + suffix;
if (looksLikeJarURL) {
foundResource = tryResourceFromJarURLWithLoadPath(namePlusSuffix, loadPathEntry);
} else if (looksLikeClasspathURL) {
foundResource = findFileInClasspath(loadPathEntry + "/" + namePlusSuffix);
} else {
foundResource = tryResourceFromLoadPath(namePlusSuffix, loadPathEntry);
}
if (foundResource != null) {
String ss = namePlusSuffix;
if(ss.startsWith("./")) {
ss = ss.substring(2);
}
state.setLoadName(resolveLoadName(foundResource, ss));
break Outer;
}
}
}
}
return foundResource;
}
protected String getLoadPathEntry(IRubyObject entry) {
return RubyFile.get_path(entry.getRuntime().getCurrentContext(), entry).asJavaString();
}
@Deprecated
protected LoadServiceResource tryResourceFromJarURLWithLoadPath(String namePlusSuffix, String loadPathEntry) {
LoadServiceResource foundResource = null;
String[] urlParts = splitJarUrl(loadPathEntry);
String jarFileName = urlParts[0];
String entryPath = urlParts[1];
JarFile current = getJarFile(jarFileName);
if (current != null ) {
String canonicalEntry = (entryPath.length() > 0 ? entryPath + "/" : "") + namePlusSuffix;
debugLogTry("resourceFromJarURLWithLoadPath", current.getName() + "!/" + canonicalEntry);
if (current.getJarEntry(canonicalEntry) != null) {
try {
URI resourceUri = new URI("jar", "file:" + jarFileName + "!/" + canonicalEntry, null);
foundResource = new LoadServiceResource(resourceUri.toURL(), resourceUri.getSchemeSpecificPart());
debugLogFound(foundResource);
} catch (URISyntaxException e) {
throw runtime.newIOError(e.getMessage());
} catch (MalformedURLException e) {
throw runtime.newIOErrorFromException(e);
}
}
}
return foundResource;
}
public JarFile getJarFile(String jarFileName) {
JarFile jarFile = jarFiles.get(jarFileName);
if(null == jarFile) {
try {
jarFile = new JarFile(jarFileName);
jarFiles.put(jarFileName, jarFile);
} catch (ZipException ignored) {
if (runtime.getInstanceConfig().isDebug()) {
LOG.info("ZipException trying to access " + jarFileName, ignored);
}
} catch (FileNotFoundException ignored) {
} catch (IOException e) {
throw runtime.newIOErrorFromException(e);
}
}
return jarFile;
}
protected boolean loadPathLooksLikeJarURL(String loadPathEntry) {
return loadPathEntry.startsWith("jar:") || loadPathEntry.endsWith(".jar") || (loadPathEntry.startsWith("file:") && loadPathEntry.indexOf("!") != -1);
}
protected boolean loadPathLooksLikeClasspathURL(String loadPathEntry) {
return loadPathEntry.startsWith("classpath:");
}
private String[] splitJarUrl(String loadPathEntry) {
String unescaped = loadPathEntry;
try {
unescaped = new URI(loadPathEntry).getSchemeSpecificPart();
} catch (URISyntaxException e) {
}
int idx = unescaped.indexOf('!');
if (idx == -1) {
return new String[]{unescaped, ""};
}
String filename = unescaped.substring(0, idx);
String entry = idx + 2 < unescaped.length() ? unescaped.substring(idx + 2) : "";
if (filename.startsWith("jar:")) {
filename = filename.substring(4);
}
if (filename.startsWith("file:")) {
filename = filename.substring(5);
}
return new String[]{filename, entry};
}
@Deprecated
protected LoadServiceResource tryResourceFromLoadPath( String namePlusSuffix,String loadPathEntry) throws RaiseException {
LoadServiceResource foundResource = null;
try {
if (!Ruby.isSecurityRestricted()) {
String reportedPath = loadPathEntry + "/" + namePlusSuffix;
boolean absolute = true;
if (!new File(reportedPath).isAbsolute()) {
absolute = false;
if (reportedPath.charAt(0) != '.') {
reportedPath = "./" + reportedPath;
}
loadPathEntry = JRubyFile.create(runtime.getCurrentDirectory(), loadPathEntry).getAbsolutePath();
}
JRubyFile actualPath = JRubyFile.create(loadPathEntry, RubyFile.expandUserPath(runtime.getCurrentContext(), namePlusSuffix));
if (RubyInstanceConfig.DEBUG_LOAD_SERVICE) {
debugLogTry("resourceFromLoadPath", "'" + actualPath.toString() + "' " + actualPath.isFile() + " " + actualPath.canRead());
}
if (actualPath.canRead()) {
foundResource = new LoadServiceResource(actualPath, reportedPath, absolute);
debugLogFound(foundResource);
}
}
} catch (SecurityException secEx) {
}
return foundResource;
}
@Deprecated
protected LoadServiceResource tryResourceAsIs(String namePlusSuffix) throws RaiseException {
return tryResourceAsIs(namePlusSuffix, "resourceAsIs");
}
@Deprecated
protected LoadServiceResource tryResourceAsIs(String namePlusSuffix, String debugName) throws RaiseException {
LoadServiceResource foundResource = null;
try {
if (!Ruby.isSecurityRestricted()) {
String reportedPath = namePlusSuffix;
File actualPath;
if (new File(reportedPath).isAbsolute()) {
actualPath = new File(RubyFile.expandUserPath(runtime.getCurrentContext(), namePlusSuffix));
} else {
if (reportedPath.charAt(0) == '.' && reportedPath.charAt(1) == '/') {
reportedPath = reportedPath.replaceFirst("\\./", runtime.getCurrentDirectory());
}
actualPath = JRubyFile.create(runtime.getCurrentDirectory(), RubyFile.expandUserPath(runtime.getCurrentContext(), namePlusSuffix));
}
debugLogTry(debugName, actualPath.toString());
if (reportedPath.contains("..")) {
try {
actualPath = actualPath.getCanonicalFile();
} catch (IOException ioe) {
}
}
if (actualPath.isFile() && actualPath.canRead()) {
foundResource = new LoadServiceResource(actualPath, reportedPath);
debugLogFound(foundResource);
}
}
} catch (SecurityException secEx) {
}
return foundResource;
}
protected LoadServiceResource findFileInClasspath(String name) {
ClassLoader classLoader = runtime.getJRubyClassLoader();
if (Ruby.isSecurityRestricted() && classLoader == null) {
classLoader = runtime.getInstanceConfig().getLoader();
}
if (name.startsWith("classpath:/")) {
LoadServiceResource foundResource = getClassPathResource(classLoader, name);
if (foundResource != null) {
return foundResource;
}
} else if (name.startsWith("classpath:")) {
name = name.substring("classpath:".length());
}
for (int i = 0; i < loadPath.size(); i++) {
String entry = getLoadPathEntry(loadPath.eltInternal(i));
if (entry.length() == 0) continue;
if (entry.charAt(0) == '/' || (entry.length() > 1 && entry.charAt(1) == ':')) continue;
if (entry.startsWith("classpath:/")) {
entry = entry.substring("classpath:/".length());
} else if (entry.startsWith("classpath:")) {
entry = entry.substring("classpath:".length());
}
String entryName;
if (name.startsWith(entry)) {
entryName = name.substring(entry.length());
} else {
entryName = name;
}
LoadServiceResource foundResource = getClassPathResource(classLoader, entry + "/" + entryName);
if (foundResource != null) {
return foundResource;
}
}
if (name.charAt(0) == '/' || (name.length() > 1 && name.charAt(1) == ':')) return null;
LoadServiceResource foundResource = getClassPathResource(classLoader, name);
if (foundResource != null) {
return foundResource;
}
return null;
}
protected static boolean isRequireable(URL loc) {
if (loc != null) {
if (loc.getProtocol().equals("file") && new java.io.File(getPath(loc)).isDirectory()) {
return false;
}
try {
loc.openConnection();
return true;
} catch (Exception e) {}
}
return false;
}
private static final Pattern URI_PATTERN = Pattern.compile("([a-z]+?://.*)$");
public LoadServiceResource getClassPathResource(ClassLoader classLoader, String name) {
boolean isClasspathScheme = false;
if (name.startsWith("classpath:/")) {
isClasspathScheme = true;
name = name.substring("classpath:/".length());
} else if (name.startsWith("classpath:")) {
isClasspathScheme = true;
name = name.substring("classpath:".length());
} else if(name.startsWith("file:") && name.indexOf("!/") != -1) {
name = name.substring(name.indexOf("!/") + 2);
}
URL loc;
Matcher m = URI_PATTERN.matcher( name );
final String pn;
if (m.matches()) {
debugLogTry("fileInClassloader", m.group( 1 ) );
try
{
loc = new URL( m.group( 1 ).replaceAll("([^:])//", "$1/") );
loc.openStream();
}
catch (IOException e)
{
loc = null;
}
}
else {
debugLogTry("fileInClasspath", name);
try
{
loc = classLoader.getResource(name);
}
catch (IllegalArgumentException e)
{
loc = null;
}
}
if (loc != null) {
String path = classpathFilenameFromURL(name, loc, isClasspathScheme);
LoadServiceResource foundResource = new LoadServiceResource(loc, path);
debugLogFound(foundResource);
return foundResource;
}
return null;
}
public static String classpathFilenameFromURL(String name, URL loc, boolean isClasspathScheme) {
String path = "classpath:/" + name;
if (!isClasspathScheme &&
(loc.getProtocol().equals("jar") || loc.getProtocol().equals("file"))
&& isRequireable(loc)) {
path = getPath(loc);
if (Platform.IS_WINDOWS && loc.getProtocol().equals("file")) {
path = new File(path).getPath();
}
}
return path;
}
private String expandRelativeJarPath(String path) {
return path.replaceAll("/[^/]+/\\.\\.|[^/]+/\\.\\./|\\./","").replace("^\\\\","/");
}
protected String resolveLoadName(LoadServiceResource foundResource, String previousPath) {
if (canGetAbsolutePath) {
try {
String path = foundResource.getAbsolutePath();
if (Platform.IS_WINDOWS) {
path = path.replace('\\', '/');
}
return path;
} catch (AccessControlException ace) {
runtime.getWarnings().warn("can't canonicalize loaded names due to security restrictions; disabling");
canGetAbsolutePath = false;
}
}
return resolveLoadName(foundResource, previousPath);
}
protected String getFileName(JRubyFile file, String namePlusSuffix) {
return file.getAbsolutePath();
}
@Deprecated
public void addBuiltinLibrary(String name, Library library) {
builtinLibraries.put(name, library);
}
@Deprecated
public void removeBuiltinLibrary(String name) {
builtinLibraries.remove(name);
}
@Deprecated
public List<String> getBuiltinLibraries() {
return builtinLibraries.keySet().stream().collect(Collectors.toList());
}
@Deprecated
protected final Map<String, Library> builtinLibraries = new HashMap<>(36);
}