Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkl-core/src/main/java/org/pkl/core/SecurityManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ public interface SecurityManager {
/** Checks if the given importing module may import the given imported module. */
void checkImportModule(URI importingModule, URI importedModule) throws SecurityManagerException;

/** Checks if the given resource may be read. */
void checkReadResource(URI resource) throws SecurityManagerException;
/** Checks if the given reading module may read the given resource . */
void checkReadResource(URI readingModule, URI resource) throws SecurityManagerException;

/**
* Checks if the given resource may be resolved. This check is required before any attempt is made
Expand Down
20 changes: 15 additions & 5 deletions pkl-core/src/main/java/org/pkl/core/SecurityManagers.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,19 +155,29 @@ public void checkResolveResource(URI resource) throws SecurityManagerException {
}

@Override
public void checkReadResource(URI uri) throws SecurityManagerException {
public void checkReadResource(URI readingModule, URI uri) throws SecurityManagerException {
var importingTrustLevel = trustLevels.apply(readingModule);
var importedTrustLevel = trustLevels.apply(uri);

if (importingTrustLevel < importedTrustLevel) {
var message =
ErrorMessages.create(
"insufficientTrustLevel", uri, readingModule, "read resource", "reading");
throw new SecurityManagerException(message);
}

checkRead(uri, allowedResources, true);
}

@Override
public void checkImportModule(URI importingModule, URI importedModule)
throws SecurityManagerException {
public void checkImportModule(URI importingModule, URI uri) throws SecurityManagerException {
var importingTrustLevel = trustLevels.apply(importingModule);
var importedTrustLevel = trustLevels.apply(importedModule);
var importedTrustLevel = trustLevels.apply(uri);

if (importingTrustLevel < importedTrustLevel) {
var message =
ErrorMessages.create("insufficientModuleTrustLevel", importedModule, importingModule);
ErrorMessages.create(
"insufficientTrustLevel", uri, importingModule, "import module", "importing");
throw new SecurityManagerException(message);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1957,16 +1957,15 @@ public UnresolvedPropertyNode visitClassProperty(ClassProperty entry) {
scope.getName(),
scope.getConstLevel() == ConstLevel.ALL));
} else { // no value given
if (isLocal) {
assert typeAnnotation != null;
throw missingLocalPropertyValue(typeAnnotation);
}
if (VmModifier.isExternal(modifiers)) {
bodyNode =
externalMemberRegistry.getPropertyBody(scope.getQualifiedName(), headerSection);
if (bodyNode instanceof LanguageAwareNode languageAwareNode) {
languageAwareNode.initLanguage(language);
}
} else if (isLocal) {
assert typeAnnotation != null;
throw missingLocalPropertyValue(typeAnnotation);
} else {
bodyNode = null; // will be given a default by UnresolvedPropertyNode
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ protected final URI parseUri(String resourceUri) {
@TruffleBoundary
protected final @Nullable Object doRead(String resourceUri, VmContext context, Node readNode) {
var resolvedUri = resolveResource(currentModule, resourceUri);
return context.getResourceManager().read(resolvedUri, readNode).orElse(null);
return context
.getResourceManager()
.read(currentModule.getUri(), resolvedUri, readNode)
.orElse(null);
}

private URI resolveResource(ModuleKey moduleKey, String resourceUri) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@
import com.oracle.truffle.api.source.SourceSection;
import java.util.Map;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.module.ModuleKey;
import org.pkl.core.runtime.VmContext;
import org.pkl.core.runtime.VmObjectLike;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.util.GlobResolver.ResolvedGlobElement;

/** Used by {@link ReadGlobNode}. */
public class ReadGlobMemberBodyNode extends ExpressionNode {
public ReadGlobMemberBodyNode(SourceSection sourceSection) {
private final ModuleKey currentModule;

public ReadGlobMemberBodyNode(SourceSection sourceSection, ModuleKey currentModule) {
super(sourceSection);
this.currentModule = currentModule;
}

@Override
Expand All @@ -44,7 +48,11 @@ private Object readResource(VmObjectLike mapping, String path) {
var globElement = VmUtils.getMapValue(globElements, path);
assert globElement != null;
var resourceUri = globElement.uri();
var resource = VmContext.get(this).getResourceManager().read(resourceUri, this).orElse(null);
var resource =
VmContext.get(this)
.getResourceManager()
.read(currentModule.getUri(), resourceUri, this)
.orElse(null);
if (resource == null) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder().evalError("cannotFindResource", resourceUri).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private SharedMemberNode getMemberNode() {
"",
language,
new FrameDescriptor(),
new ReadGlobMemberBodyNode(sourceSection));
new ReadGlobMemberBodyNode(sourceSection, currentModule));
}
return memberNode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,13 @@ protected InputStream openExternalUri(URI uri) throws SecurityManagerException {
}

// treat package assets as resources instead of modules
securityManager.checkReadResource(uri);
securityManager.checkResolveResource(uri);
var request = HttpRequest.newBuilder(uri).build();
HttpResponse<InputStream> response;
try {
response =
httpClient.send(
request, BodyHandlers.ofInputStream(), securityManager::checkReadResource);
request, BodyHandlers.ofInputStream(), securityManager::checkResolveResource);
} catch (IOException e) {
throw new PackageLoadError(e, "ioErrorMakingHttpGet", uri, e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ public Optional<Object> read(URI uri)
var request = HttpRequest.newBuilder(uri).build();
var response =
httpClient.send(
request, BodyHandlers.ofByteArray(), securityManager::checkReadResource);
request, BodyHandlers.ofByteArray(), securityManager::checkResolveResource);
if (response.statusCode() == 404) return Optional.empty();
HttpUtils.checkHasStatusCode200(response);
return Optional.of(new Resource(uri, response.body()));
Expand Down Expand Up @@ -550,7 +550,7 @@ public Optional<Object> read(URI uri)
if (local != null) {
var resourceManager = VmContext.get(null).getResourceManager();
var securityManager = VmContext.get(null).getSecurityManager();
securityManager.checkReadResource(local);
securityManager.checkResolveResource(local);
var reader = resourceManager.getResourceReader(local);
if (reader == null) {
throw new VmExceptionBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public static VmClass getImportClass() {
return ImportClass.instance;
}

public static VmClass getReadClass() {
return ReadClass.instance;
}

private static final class CommandInfoClass {
static final VmClass instance = loadClass("CommandInfo");
}
Expand Down Expand Up @@ -87,6 +91,10 @@ private static final class ImportClass {
static final VmClass instance = loadClass("Import");
}

private static final class ReadClass {
static final VmClass instance = loadClass("Read");
}

@TruffleBoundary
private static VmClass loadClass(String className) {
var theModule = getModule();
Expand Down
142 changes: 96 additions & 46 deletions pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.externalreader.ExternalReaderProcessException;
import org.pkl.core.http.HttpClientException;
import org.pkl.core.module.ModuleKeys;
import org.pkl.core.module.ResolvedModuleKey;
import org.pkl.core.util.EconomicMaps;
Expand Down Expand Up @@ -487,14 +488,17 @@ public OptionBehavior(@Nullable VmTyped annotation, boolean hasMetavar) {
? null
: VmUtils.readMember(annotation, Identifier.CONVERT) instanceof VmFunction func
? (rawValue, workingDirUri) ->
handleBadValue(() -> handleImports(func.apply(rawValue), workingDirUri))
handleBadValue(
() -> handleImportsAndReads(func.apply(rawValue), workingDirUri))
: null,
annotation == null
? null
: VmUtils.readMember(annotation, Identifier.TRANSFORM_ALL) instanceof VmFunction func
? (values, workingDirUri) ->
handleBadValue(
() -> handleImports(func.apply(VmList.create(values)), workingDirUri))
() ->
handleImportsAndReads(
func.apply(VmList.create(values)), workingDirUri))
: null,
annotation == null
? null
Expand Down Expand Up @@ -1061,15 +1065,12 @@ private CommandSpec.Result evaluateResult(VmTyped module, SubcommandState parent
}

// endregion
// region dynamic import handling

private static boolean isImport(VmTyped value) {
return value.getVmClass() == CommandModule.getImportClass();
}
// region dynamic import/read handling

private static boolean isImport(Object value) {
private static boolean isImportOrRead(Object value) {
return value instanceof VmTyped vmTyped
&& vmTyped.getVmClass() == CommandModule.getImportClass();
&& (vmTyped.getVmClass() == CommandModule.getImportClass()
|| vmTyped.getVmClass() == CommandModule.getReadClass());
}

// handle errors from convert/transformAll and correctly format them for the CLI
Expand Down Expand Up @@ -1101,79 +1102,83 @@ private <T> T handleErrors(Supplier<T> f) {
}
}

private Object handleImportOrRead(Object val, URI workingDirUri) {
if (!(val instanceof VmTyped vmTyped)) return val;
if (vmTyped.getVmClass() == CommandModule.getImportClass()) {
return handleImport(vmTyped, workingDirUri);
}
if (vmTyped.getVmClass() == CommandModule.getReadClass()) {
return handleRead(vmTyped, workingDirUri);
}
return val;
}

// for convert, handle imports by replacing Command.Import values
// with imported module or Mapping<String, Module> values
// Command.Import instances in returned Pair, List, Set, or Map values are replaced as well
// other types or nested instances of the above are not affected
private Object handleImports(Object result, URI workingDirUri) {
if (result instanceof VmTyped vmTyped && isImport(vmTyped)) {
return handleImport(vmTyped, workingDirUri);
} else if (result instanceof VmPair vmPair) {
if (!isImport(vmPair.getFirst()) && !isImport(vmPair.getSecond())) {
private Object handleImportsAndReads(Object result, URI workingDirUri) {
if (result instanceof VmPair vmPair) {
if (!isImportOrRead(vmPair.getFirst()) && !isImportOrRead(vmPair.getSecond())) {
return vmPair;
}
return new VmPair(
isImport(vmPair.getFirst())
? handleImport((VmTyped) vmPair.getFirst(), workingDirUri)
: vmPair.getFirst(),
isImport(vmPair.getSecond())
? handleImport((VmTyped) vmPair.getSecond(), workingDirUri)
: vmPair.getSecond());
handleImportOrRead(vmPair.getFirst(), workingDirUri),
handleImportOrRead(vmPair.getSecond(), workingDirUri));
} else if (result instanceof VmCollection vmCollection) {
for (var elem : vmCollection) {
if (isImport(elem)) {
if (isImportOrRead(elem)) {
var builder = vmCollection.builder();
vmCollection.forEach(
it -> builder.add(isImport(it) ? handleImport((VmTyped) it, workingDirUri) : it));
vmCollection.forEach(it -> builder.add(handleImportOrRead(it, workingDirUri)));
return builder.build();
}
}
return vmCollection;
} else if (result instanceof VmMap vmMap) {
for (var entry : vmMap) {
if (isImport(entry.getKey()) || isImport(entry.getValue())) {
if (isImportOrRead(entry.getKey()) || isImportOrRead(entry.getValue())) {
var builder = VmMap.builder();
vmMap.forEach(
it ->
builder.add(
isImport(it.getKey())
? handleImport((VmTyped) it.getKey(), workingDirUri)
: it.getKey(),
isImport(it.getValue())
? handleImport((VmTyped) it.getValue(), workingDirUri)
: it.getValue()));
handleImportOrRead(it.getKey(), workingDirUri),
handleImportOrRead(it.getValue(), workingDirUri)));
return builder.build();
}
}
}
return result;
return handleImportOrRead(result, workingDirUri);
}

private Object handleImport(VmTyped mport, URI workingDirUri) {
var moduleName = (String) VmUtils.readMember(mport, Identifier.URI);
String uriString;
private URI getModuleUriString(
VmTyped directive, URI workingDirUri, String errorKey, boolean checkTripleDot) {
var name = (String) VmUtils.readMember(directive, Identifier.URI);

if (checkTripleDot && name.startsWith("...")) {
throw exceptionBuilder().evalError("cannotGlobTripleDots").build();
}

// Ported from org.pkl.cli.commons.cli.commands.BaseOptions:
try {
// Can't just use URI constructor, because URI(null, null, "C:/foo/bar", null) turns
// into `URI("C", null, "/foo/bar", null)`.
@SuppressWarnings("DuplicateExpressions")
var uri =
IoUtils.isUriLike(moduleName)
? new URI(moduleName)
: IoUtils.isWindows() && IoUtils.isWindowsAbsolutePath(moduleName)
? Path.of(moduleName).toUri()
: new URI(null, null, IoUtils.toNormalizedPathString(Path.of(moduleName)), null);
uriString =
uri.isAbsolute() ? uri.toString() : IoUtils.resolve(workingDirUri, uri).toString();
IoUtils.isUriLike(name)
? new URI(name)
: IoUtils.isWindows() && IoUtils.isWindowsAbsolutePath(name)
? Path.of(name).toUri()
: new URI(null, null, IoUtils.toNormalizedPathString(Path.of(name)), null);
return uri.isAbsolute() ? uri : IoUtils.resolve(workingDirUri, uri);
} catch (URISyntaxException e) {
throw exceptionBuilder()
.evalError("invalidModuleUri", moduleName)
.withHint(e.getReason())
.build();
throw exceptionBuilder().evalError(errorKey, name).withHint(e.getReason()).build();
}
}

private Object handleImport(VmTyped mport, URI workingDirUri) {
var importUri = getModuleUriString(mport, workingDirUri, "invalidModuleUri", false);
var uriString = importUri.toString();
var isGlob = (Boolean) VmUtils.readMember(mport, Identifier.GLOB);
var importUri = URI.create(uriString);
var language = VmLanguage.get(null);

// non-glob
Expand Down Expand Up @@ -1214,6 +1219,51 @@ private Object handleImport(VmTyped mport, URI workingDirUri) {
}
}

private Object handleRead(VmTyped read, URI workingDirUri) {
var type = (String) VmUtils.readMember(read, Identifier.TYPE);
var isGlob = "glob".equals(type);
var uri = getModuleUriString(read, workingDirUri, "invalidResourceUri", isGlob);
var uriString = uri.toString();
var context = VmContext.get(null);

// non-glob
if (!isGlob) {
var resource = context.getResourceManager().read(REPL_TEXT_URI, uri, null);
if ("nullable".equals(type)) return resource.orElse(VmNull.withoutDefault());
if (resource.isEmpty()) {
throw exceptionBuilder().evalError("cannotFindResource", uriString).build();
}
return resource.get();
}

// glob
try {
var reader = context.getResourceManager().getReader(uri, null);
if (!reader.isGlobbable()) {
throw exceptionBuilder().evalError("cannotGlobUri", uri, uri.getScheme()).build();
}
var resolvedElements =
GlobResolver.resolveGlob(context.getSecurityManager(), reader, null, null, uriString);

var builder = new VmObjectBuilder(resolvedElements.size());
for (var entry : resolvedElements.entrySet()) {
builder.addEntry(entry.getKey(), reader.read(entry.getValue().uri()));
}
return builder.toMapping(resolvedElements);
} catch (IOException e) {
throw exceptionBuilder().evalError("ioErrorResolvingGlob", uriString).withCause(e).build();
} catch (SecurityManagerException | HttpClientException | URISyntaxException e) {
throw exceptionBuilder().withCause(e).build();
} catch (InvalidGlobPatternException e) {
throw exceptionBuilder()
.evalError("invalidGlobPattern", uriString)
.withHint(e.getMessage())
.build();
} catch (ExternalReaderProcessException e) {
throw exceptionBuilder().evalError("externalReaderFailure").withCause(e).build();
}
}

// endregion
// region utilities

Expand Down
Loading
Loading