mirror of
https://github.com/apple/pkl.git
synced 2026-01-11 22:30:54 +01:00
Treat opaque file URIs as errors (#1087)
Opaque file URIs are URIs whose scheme-specific part does not start with `/`. For example, `file:foo/bar.txt` is an opaque URI. Currently, this has the unintentional behavior of: look for file `foo/bar.txt` from the process working directory. These are effectively dynamics imports; from a single import, we can't statically analyze what it resolves as. According to RFC-8089, File URIs must have paths that start with `/`. So, these are actually _not valid URIs_. See the grammar defined in https://datatracker.ietf.org/doc/html/rfc8089#section-2 This changes Pkl's behavior so that these URIs are treated as errors.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -75,7 +75,7 @@ public class ImportGlobNode extends AbstractImportNode {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
var context = VmContext.get(this);
|
||||
try {
|
||||
var moduleKey = context.getModuleResolver().resolve(importUri);
|
||||
var moduleKey = context.getModuleResolver().resolve(importUri, this);
|
||||
if (!moduleKey.isGlobbable()) {
|
||||
throw exceptionBuilder()
|
||||
.evalError("cannotGlobUri", importUri, importUri.getScheme())
|
||||
|
||||
@@ -173,7 +173,7 @@ public final class ModuleKeyFactories {
|
||||
|
||||
private static class File implements ModuleKeyFactory {
|
||||
@Override
|
||||
public Optional<ModuleKey> create(URI uri) {
|
||||
public Optional<ModuleKey> create(URI uri) throws URISyntaxException {
|
||||
// skip loading providers if the scheme is `file`.
|
||||
if (uri.getScheme().equalsIgnoreCase("file")) {
|
||||
return Optional.of(ModuleKeys.file(uri));
|
||||
|
||||
@@ -25,8 +25,10 @@ import java.net.URISyntaxException;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.pkl.core.PklBugException;
|
||||
import org.pkl.core.SecurityManager;
|
||||
import org.pkl.core.SecurityManagerException;
|
||||
import org.pkl.core.externalreader.ExternalModuleResolver;
|
||||
@@ -90,10 +92,20 @@ public final class ModuleKeys {
|
||||
}
|
||||
|
||||
/** Creates a module key for a {@code file:} module. */
|
||||
public static ModuleKey file(URI uri) {
|
||||
public static ModuleKey file(URI uri) throws URISyntaxException {
|
||||
return new File(uri);
|
||||
}
|
||||
|
||||
/** Creates a module key for a {@code file:} module. */
|
||||
public static ModuleKey file(Path path) {
|
||||
try {
|
||||
return new File(path.toAbsolutePath().toUri());
|
||||
} catch (URISyntaxException e) {
|
||||
// impossible, we started with a path to begin with.
|
||||
throw PklBugException.unreachableCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a module key for a {@code modulepath:} module to be resolved with the given resolver.
|
||||
*/
|
||||
@@ -299,7 +311,8 @@ public final class ModuleKeys {
|
||||
private static class File implements ModuleKey {
|
||||
final URI uri;
|
||||
|
||||
File(URI uri) {
|
||||
File(URI uri) throws URISyntaxException {
|
||||
IoUtils.validateFileUri(uri);
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -443,7 +443,7 @@ public final class ProjectPackager {
|
||||
|
||||
private @Nullable List<ImportsAndReadsParser.Entry> getImportsAndReads(Path pklModulePath) {
|
||||
try {
|
||||
var moduleKey = ModuleKeys.file(pklModulePath.toUri());
|
||||
var moduleKey = ModuleKeys.file(pklModulePath);
|
||||
var resolvedModuleKey = ResolvedModuleKeys.file(moduleKey, moduleKey.getUri(), pklModulePath);
|
||||
return ImportsAndReadsParser.parse(moduleKey, resolvedModuleKey);
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -254,6 +254,12 @@ public final class ResourceReaders {
|
||||
private static final class FileResource extends UrlResource {
|
||||
static final ResourceReader INSTANCE = new FileResource();
|
||||
|
||||
@Override
|
||||
public Optional<Object> read(URI uri) throws IOException, URISyntaxException {
|
||||
IoUtils.validateFileUri(uri);
|
||||
return super.read(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUriScheme() {
|
||||
return "file";
|
||||
@@ -324,7 +330,7 @@ public final class ResourceReaders {
|
||||
|
||||
private abstract static class UrlResource implements ResourceReader {
|
||||
@Override
|
||||
public Optional<Object> read(URI uri) throws IOException {
|
||||
public Optional<Object> read(URI uri) throws IOException, URISyntaxException {
|
||||
if (HttpUtils.isHttpUrl(uri)) {
|
||||
var httpClient = VmContext.get(null).getHttpClient();
|
||||
var request = HttpRequest.newBuilder(uri).build();
|
||||
|
||||
@@ -436,7 +436,8 @@ public final class GlobResolver {
|
||||
|
||||
/** Split a glob pattern into the base, non-wildcard parts, and the wildcard parts. */
|
||||
private static Pair<String, String[]> splitGlobPatternIntoBaseAndWildcards(
|
||||
ReaderBase reader, String globPattern, boolean hasAbsoluteGlob) {
|
||||
ReaderBase reader, String globPattern, boolean hasAbsoluteGlob)
|
||||
throws InvalidGlobPatternException {
|
||||
var effectiveGlobPattern = globPattern;
|
||||
var basePathSb = new StringBuilder();
|
||||
if (hasAbsoluteGlob) {
|
||||
@@ -446,6 +447,10 @@ public final class GlobResolver {
|
||||
basePathSb.append(IoUtils.stripFragment(globUri)).append('#');
|
||||
} else {
|
||||
effectiveGlobPattern = globUri.getPath();
|
||||
if (effectiveGlobPattern == null) {
|
||||
throw new InvalidGlobPatternException(
|
||||
ErrorMessages.create("invalidGlobNonHierarchicalUri", globUri.getScheme()));
|
||||
}
|
||||
basePathSb.append(globUri.getScheme()).append(':');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,4 +835,10 @@ public final class IoUtils {
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
public static void validateFileUri(URI uri) throws URISyntaxException {
|
||||
if (!uri.getSchemeSpecificPart().startsWith("/")) {
|
||||
throw new URISyntaxException(uri.toString(), ErrorMessages.create("invalidOpaqueFileUri"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,6 +661,9 @@ Invalid escape character `\\{0}`.
|
||||
invalidGlobTooComplex=\
|
||||
The glob pattern is too complex.
|
||||
|
||||
invalidGlobNonHierarchicalUri=\
|
||||
Scheme `{0}` requires a hierarchical path (there must be a `/` after the first colon).
|
||||
|
||||
# used as {1} in invalidModuleUri and invalidResourceUri
|
||||
invalidTripleDotSyntax=\
|
||||
expected `...` or `.../path/to/my_module.pkl`
|
||||
@@ -1057,3 +1060,7 @@ External {0} reader does not support scheme `{1}`.
|
||||
|
||||
externalReaderAlreadyTerminated=\
|
||||
External reader process has already terminated.
|
||||
|
||||
invalidOpaqueFileUri=\
|
||||
File URIs must have a path that starts with `/` (e.g. file:/path/to/my_module.pkl).\n\
|
||||
To resolve relative paths, remove the scheme prefix (remove "file:").
|
||||
|
||||
1
pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidFileUri1.pkl
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidFileUri1.pkl
vendored
Normal file
@@ -0,0 +1 @@
|
||||
res = read("file:path/to/foo.txt")
|
||||
1
pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidFileUri2.pkl
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidFileUri2.pkl
vendored
Normal file
@@ -0,0 +1 @@
|
||||
res = read*("file:path/to/foo.txt")
|
||||
1
pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidFileUri3.pkl
vendored
Normal file
1
pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidFileUri3.pkl
vendored
Normal file
@@ -0,0 +1 @@
|
||||
res = import("file:path/to/foo.pkl")
|
||||
9
pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidFileUri4.pkl
vendored
Normal file
9
pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidFileUri4.pkl
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// Ideally, this should have the same error message as `invalidFileUri2`; the error is that the glob
|
||||
// pattern is invalid.
|
||||
//
|
||||
// But, this is somewhat challenging to fix, because the `URISyntaxException` gets thrown before
|
||||
// `ImportGlobNode` is initialized.
|
||||
//
|
||||
// Regardless, this error is good enough (given this error message, users know what to do), and we
|
||||
// can afford to be pragmatic here.
|
||||
res = import*("file:path/to/foo.pkl")
|
||||
13
pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidFileUri1.err
vendored
Normal file
13
pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidFileUri1.err
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
–– Pkl Error ––
|
||||
Resource URI `file:path/to/foo.txt` has invalid syntax.
|
||||
|
||||
x | res = read("file:path/to/foo.txt")
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at invalidFileUri1#res (file:///$snippetsDir/input/errors/invalidFileUri1.pkl)
|
||||
|
||||
File URIs must have a path that starts with `/` (e.g. file:/path/to/my_module.pkl).
|
||||
To resolve relative paths, remove the scheme prefix (remove "file:").
|
||||
|
||||
xxx | text = renderer.renderDocument(value)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at pkl.base#Module.output.text (pkl:base)
|
||||
12
pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidFileUri2.err
vendored
Normal file
12
pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidFileUri2.err
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
–– Pkl Error ––
|
||||
Invalid glob pattern `file:path/to/foo.txt`.
|
||||
|
||||
x | res = read*("file:path/to/foo.txt")
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at invalidFileUri2#res (file:///$snippetsDir/input/errors/invalidFileUri2.pkl)
|
||||
|
||||
Scheme `file` requires a hierarchical path (there must be a `/` after the first colon).
|
||||
|
||||
xxx | text = renderer.renderDocument(value)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at pkl.base#Module.output.text (pkl:base)
|
||||
13
pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidFileUri3.err
vendored
Normal file
13
pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidFileUri3.err
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
–– Pkl Error ––
|
||||
Module URI `file:path/to/foo.pkl` has invalid syntax.
|
||||
|
||||
x | res = import("file:path/to/foo.pkl")
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at invalidFileUri3#res (file:///$snippetsDir/input/errors/invalidFileUri3.pkl)
|
||||
|
||||
File URIs must have a path that starts with `/` (e.g. file:/path/to/my_module.pkl).
|
||||
To resolve relative paths, remove the scheme prefix (remove "file:").
|
||||
|
||||
xxx | text = renderer.renderDocument(value)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at pkl.base#Module.output.text (pkl:base)
|
||||
13
pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidFileUri4.err
vendored
Normal file
13
pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidFileUri4.err
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
–– Pkl Error ––
|
||||
Module URI `file:path/to/foo.pkl` has invalid syntax.
|
||||
|
||||
x | res = import*("file:path/to/foo.pkl")
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at invalidFileUri4#res (file:///$snippetsDir/input/errors/invalidFileUri4.pkl)
|
||||
|
||||
File URIs must have a path that starts with `/` (e.g. file:/path/to/my_module.pkl).
|
||||
To resolve relative paths, remove the scheme prefix (remove "file:").
|
||||
|
||||
xxx | text = renderer.renderDocument(value)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at pkl.base#Module.output.text (pkl:base)
|
||||
Reference in New Issue
Block a user