Add support for HTTP proxying (#506)

* Add `--proxy` and `--no-proxy` CLI flags
* Add property `http` to `pkl:settings`
* Move `EvaluatorSettings` from `pkl:Project` to its own module and add property `http`
* Add support for proxying in server mode, and through Gradle
* Add `setProxy()` to `HttpClient`
* Add documentation
This commit is contained in:
Philip K.F. Hölzenspies
2024-06-12 19:54:22 +01:00
committed by GitHub
parent a520ae7d04
commit b03530ed1f
61 changed files with 1581 additions and 412 deletions

View File

@@ -2,12 +2,12 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.tunnelvisionlabs:antlr4-runtime:4.9.0=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
net.sf.jopt-simple:jopt-simple:5.0.4=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath
org.apache.commons:commons-math3:3.6.1=jmh,jmhCompileClasspath,jmhImplementationDependenciesMetadata,jmhRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.compiler:compiler:23.0.2=graal
org.graalvm.sdk:graal-sdk:23.0.2=graal,jmh,jmhRuntimeClasspath,truffle
org.graalvm.truffle:truffle-api:23.0.2=graal,jmh,jmhRuntimeClasspath,truffle

View File

@@ -3,10 +3,10 @@
# This file is expected to be part of source control.
com.tunnelvisionlabs:antlr4-runtime:4.9.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
io.leangen.geantyref:geantyref:1.3.15=testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.sdk:graal-sdk:23.0.2=testRuntimeClasspath
org.graalvm.truffle:truffle-api:23.0.2=testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -121,6 +121,9 @@ outputFormat: String?
/// The project dependency settings.
project: Project?
/// Configuration of outgoing HTTP requests.
http: Http?
class ClientResourceReader {
/// The URI scheme this reader is responsible for reading.
scheme: String
@@ -175,6 +178,54 @@ class Project {
dependencies: Mapping<String, Project|RemoteDependency>
}
/// Settings that control how Pkl talks to HTTP(S) servers.
class Http {
/// Configuration of the HTTP proxy to use.
///
/// If [null], uses the operating system's proxy configuration.
proxy: Proxy?
}
/// Settings that control how Pkl talks to HTTP proxies.
class Proxy {
/// The proxy to use for HTTP(S) connections.
///
/// At the moment, only HTTP proxies are supported.
///
/// Example:
/// ```
/// address = "http://my.proxy.example.com:5080"
/// ```
address: Uri(startsWith("http://"))?
/// Hosts to which all connections should bypass a proxy.
///
/// Values can be either hostnames, or IP addresses.
/// IP addresses can optionally be provided using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation).
///
/// The only wildcard is `"*"`, which disables all proxying.
///
/// A hostname matches all subdomains.
/// For example, `example.com` matches `foo.example.com`, but not `fooexample.com`.
/// A hostname that is prefixed with a dot matches the hostname itself,
/// so `.example.com` matches `example.com`.
///
/// Optionally, a port can be specified.
/// If a port is omitted, all ports are matched.
///
/// Example:
///
/// ```
/// noProxy {
/// "127.0.0.1"
/// "169.254.0.0/16"
/// "example.com"
/// "localhost:5050"
/// }
/// ```
noProxy: Listing<String>(isDistinct)
}
class RemoteDependency {
type: "remote"

View File

@@ -829,3 +829,18 @@ These certificates can be overridden via either of the two options:
Both these options will *replace* the default CA certificates bundled with Pkl. +
The CLI option takes precedence over the certificates in `~/.pkl/cacerts/`. +
Certificates need to be X.509 certificates in PEM format.
[[http-proxy]]
== HTTP Proxy
When making HTTP(S) requests, Pkl can use a proxy.
By default, no proxy is configured.
A proxy can be configured as follows:
- Using `--proxy <uri>`.
The URI must (currently) have scheme `http`, and may not contain anything other than a host and port.
- Using `--no-proxy <hosts>`.
The given (comma separated list of) hosts bypass the proxy.
Hosts can be specified by domain name (in which case all subdomains also bypass the proxy), IP addresses, or IP ranges (using link:https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation[CIDR notation]).
For individual hosts (not CIDRs), ports can be specified.
When no port is specified for a given host, connections to all ports bypass the proxy.

View File

@@ -122,3 +122,21 @@ Certificates need to be X.509 certificates in PEM format.
For other methods of configuring certificates, see xref:pkl-cli:index.adoc#ca-certs[CA Certificates].
====
.--proxy
[%collapsible]
====
Default: (none) +
Example: `http://proxy.example.com:1234` +
Configures HTTP connections to connect to the provided proxy address.
The URI must have scheme `http`, and may not contain anything other than a host and port.
====
.--no-proxy
[%collapsible]
====
Default: (none) +
Example: `example.com,169.254.0.0/16` +
Comma separated list of hosts to which all connections should bypass the proxy.
Hosts can be specified by name, IP address, or IP range using https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation[CIDR notation].
====

View File

@@ -83,3 +83,21 @@ Example: `modulePath.from files("dir1", "zip1.zip", "jar1.jar")` +
The directories, ZIP archives, or JAR archives to search when resolving `modulepath:` URIs.
Relative paths are resolved against the project directory.
====
.proxy: Property<URI>
[%collapsible]
====
Default: `null` +
Example: `http://proxy.example.com:1234` +
Configures HTTP connections to connect to the provided proxy address.
The URI must have scheme `http`, and may not contain anything other than a host and port.
====
.noProxy: ListProperty<String>
[%collapsible]
====
Default: `null` +
Example: `example.com,169.254.0.0/16` +
Hosts to which all connections should bypass the proxy.
Hosts can be specified by name, IP address, or IP range using https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation[CIDR notation].
====

View File

@@ -47,6 +47,7 @@ slf4j = "1.+"
# Breaking change in snakeYaml 2.6 (removing DumpSettingsBuilder::setScalarResolver), so pin to 2.5
snakeYaml = "2.5"
spotlessPlugin = "6.11.0"
wiremock = "3.+"
[libraries] # ordered alphabetically
antlr = { group = "com.tunnelvisionlabs", name = "antlr4", version.ref = "antlr" }
@@ -94,6 +95,7 @@ spotlessPlugin = { group = "com.diffplug.spotless", name = "spotless-plugin-grad
svm = { group = "org.graalvm.nativeimage", name = "svm", version.ref = "graalVm" }
truffleApi = { group = "org.graalvm.truffle", name = "truffle-api", version.ref = "graalVm" }
truffleDslProcessor = { group = "org.graalvm.truffle", name = "truffle-dsl-processor", version.ref = "graalVm" }
wiremock = { group = "org.wiremock", name = "wiremock", version.ref = "wiremock" }
[plugins] # ordered alphabetically
checksum = { id = "org.gradle.crypto.checksum", version.ref = "checksumPlugin" }

View File

@@ -1,10 +1,10 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -1,13 +1,59 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.ethlo.time:itu:1.8.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.17.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.github.ajalt.clikt:clikt-jvm:3.5.1=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.ajalt.clikt:clikt:3.5.1=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.github.jknack:handlebars-helpers:4.3.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.github.jknack:handlebars:4.3.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.26.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.google.guava:failureaccess:1.0.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.google.guava:guava:33.2.0-jre=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.google.j2objc:j2objc-annotations:3.0.0=testCompileClasspath,testImplementationDependenciesMetadata
com.jayway.jsonpath:json-path:2.9.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.networknt:json-schema-validator:1.4.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
commons-fileupload:commons-fileupload:1.5=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
commons-io:commons-io:2.11.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
net.javacrumbs.json-unit:json-unit-core:2.38.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.minidev:accessors-smart:2.5.1=testRuntimeClasspath
net.minidev:json-smart:2.5.1=testRuntimeClasspath
net.sf.jopt-simple:jopt-simple:5.0.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.apache.httpcomponents.client5:httpclient5:5.3.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.apache.httpcomponents.core5:httpcore5-h2:5.2.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.apache.httpcomponents.core5:httpcore5:5.2.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.checkerframework:checker-qual:3.42.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty.http2:http2-common:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty.http2:http2-hpack:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty.http2:http2-server:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty.toolchain:jetty-jakarta-servlet-api:5.0.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-alpn-client:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-alpn-java-client:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-alpn-java-server:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-alpn-server:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-bom:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-client:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-http:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-io:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-proxy:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-security:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-server:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-servlet:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-servlets:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-util:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-webapp:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.eclipse.jetty:jetty-xml:11.0.20=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.fusesource.jansi:jansi:2.4.0=default
org.fusesource.jansi:jansi:2.4.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.compiler:compiler:23.0.2=compileClasspath,compileOnlyDependenciesMetadata
@@ -17,6 +63,8 @@ org.graalvm.nativeimage:pointsto:23.0.2=compileClasspath,compileOnlyDependencies
org.graalvm.nativeimage:svm:23.0.2=compileClasspath,compileOnlyDependenciesMetadata
org.graalvm.sdk:graal-sdk:23.0.2=compileClasspath,compileOnlyDependenciesMetadata,default,runtimeClasspath,testRuntimeClasspath
org.graalvm.truffle:truffle-api:23.0.2=compileClasspath,compileOnlyDependenciesMetadata,default,runtimeClasspath,testRuntimeClasspath
org.hamcrest:hamcrest-core:2.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.hamcrest:hamcrest:2.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
@@ -32,14 +80,10 @@ org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,compileCl
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains:annotations:13.0=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jline:jline-native:3.23.0=default
org.jline:jline-native:3.23.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jline:jline-reader:3.23.0=default
org.jline:jline-reader:3.23.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jline:jline-terminal-jansi:3.23.0=default
org.jline:jline-terminal-jansi:3.23.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jline:jline-terminal:3.23.0=default
org.jline:jline-terminal:3.23.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jline:jline-native:3.23.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jline:jline-reader:3.23.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jline:jline-terminal-jansi:3.23.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jline:jline-terminal:3.23.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
org.junit.jupiter:junit-jupiter-engine:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
org.junit.jupiter:junit-jupiter-params:5.10.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
@@ -49,5 +93,12 @@ org.junit:junit-bom:5.10.2=testCompileClasspath,testImplementationDependenciesMe
org.msgpack:msgpack-core:0.9.0=default,runtimeClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
org.organicdesign:Paguro:3.10.3=default,runtimeClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.11=testRuntimeClasspath
org.slf4j:slf4j-api:2.0.9=testCompileClasspath,testImplementationDependenciesMetadata
org.snakeyaml:snakeyaml-engine:2.5=default,runtimeClasspath,testRuntimeClasspath
org.wiremock:wiremock:3.6.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.xmlunit:xmlunit-core:2.10.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.xmlunit:xmlunit-legacy:2.10.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.xmlunit:xmlunit-placeholders:2.10.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.yaml:snakeyaml:2.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
empty=annotationProcessor,archives,compile,intransitiveDependenciesMetadata,javaExecutable,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDef,kotlinScriptDefExtensions,runtime,runtimeOnlyDependenciesMetadata,shadow,signatures,sourcesJar,stagedAlpineLinuxAmd64Executable,stagedLinuxAarch64Executable,stagedLinuxAmd64Executable,stagedMacAarch64Executable,stagedMacAmd64Executable,testAnnotationProcessor,testApiDependenciesMetadata,testCompile,testCompileOnly,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDef,testKotlinScriptDefExtensions,testRuntime

View File

@@ -56,6 +56,7 @@ dependencies {
}
testImplementation(projects.pklCommonsTest)
testImplementation(libs.wiremock)
fun executableDir(name: String) = files(layout.buildDirectory.dir("executable/$name"))
stagedMacAmd64Executable(executableDir("pkl-macos-amd64"))

View File

@@ -25,7 +25,7 @@ import org.pkl.server.Server
class CliServer(options: CliBaseOptions) : CliCommand(options) {
override fun doRun() =
try {
val server = Server(MessageTransports.stream(System.`in`, System.out), httpClient)
val server = Server(MessageTransports.stream(System.`in`, System.out))
server.use { it.start() }
} catch (e: ProtocolException) {
throw CliException(e.message!!)

View File

@@ -15,17 +15,23 @@
*/
package org.pkl.cli
import com.github.tomakehurst.wiremock.client.WireMock.*
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo
import com.github.tomakehurst.wiremock.junit5.WireMockTest
import java.io.StringReader
import java.io.StringWriter
import java.net.ServerSocket
import java.net.URI
import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration
import java.util.regex.Pattern
import kotlin.io.path.*
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatCode
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.condition.DisabledOnOs
@@ -40,8 +46,10 @@ import org.pkl.commons.cli.CliException
import org.pkl.commons.test.FileTestUtils
import org.pkl.commons.test.PackageServer
import org.pkl.core.OutputFormat
import org.pkl.core.SecurityManagers
import org.pkl.core.util.IoUtils
@WireMockTest(httpsEnabled = true, proxyMode = true)
class CliEvaluatorTest {
companion object {
private val defaultContents =
@@ -1262,6 +1270,196 @@ result = someLib.x
.hasMessageNotContainingAny("java.", "sun.") // class names have been filtered out
}
@Test
fun `eval http module from proxy`(wwRuntimeInfo: WireMockRuntimeInfo) {
stubFor(
get(urlEqualTo("/bar.pkl")).withHost(equalTo("not.a.valid.host")).willReturn(ok("foo = 1"))
)
val options =
CliEvaluatorOptions(
CliBaseOptions(
sourceModules = listOf(URI("http://not.a.valid.host/bar.pkl")),
proxyAddress = URI("http://localhost:${wwRuntimeInfo.httpPort}"),
allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"),
),
)
val output = evalToConsole(options)
assertThat(output).isEqualTo("foo = 1\n")
}
@Test
fun `eval https -- no proxy`(wwRuntimeInfo: WireMockRuntimeInfo) {
// pick an address on the local machine so we can be sure this test is not making any outbound
// connections.
val openPort = ServerSocket(0).use { it.localPort }
val targetAddress = "https://127.0.0.1:$openPort"
val options =
CliEvaluatorOptions(
CliBaseOptions(
// use loopback address to prevent test from making outbound http connection.
sourceModules = listOf(URI("$targetAddress/foo.pkl")),
proxyAddress = URI(wwRuntimeInfo.httpBaseUrl),
noProxy = listOf("*"),
allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"),
)
)
assertThatCode { evalToConsole(options) }
.hasMessageContaining("I/O error loading module `$targetAddress/foo.pkl`")
}
@Test
@Disabled // TODO: figure out why this is failing.
fun `eval package from proxy`(wwRuntimeInfo: WireMockRuntimeInfo) {
stubFor(
any(anyUrl()).willReturn(aResponse().proxiedFrom("https://localhost:${packageServer.port}"))
)
val options =
CliEvaluatorOptions(
CliBaseOptions(
sourceModules = listOf(URI("package://localhost:1/birds@0.5.0#/catalog/Ostritch.pkl")),
noCache = true,
proxyAddress = URI(wwRuntimeInfo.httpBaseUrl),
caCertificates = listOf(FileTestUtils.selfSignedCertificate),
allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:")
)
)
val output = evalToConsole(options)
assertThat(output)
.isEqualTo(
"""
name = "Ostritch"
favoriteFruit {
name = "Orange"
}
"""
.trimIndent()
)
verify(getRequestedFor(urlEqualTo("birds@0.5.0")))
verify(getRequestedFor(urlEqualTo("fruit@1.0.5")))
}
@Test
fun `eval http module from proxy -- configured in settings`(
@TempDir tempDir: Path,
wwRuntimeInfo: WireMockRuntimeInfo
) {
val settingsModule =
tempDir.writeFile(
"settings.pkl",
"""
amends "pkl:settings"
http {
proxy {
address = "${wwRuntimeInfo.httpBaseUrl}"
}
}
"""
.trimIndent()
)
stubFor(
get(urlEqualTo("/bar.pkl")).withHost(equalTo("not.a.valid.host")).willReturn(ok("foo = 1"))
)
val options =
CliEvaluatorOptions(
CliBaseOptions(
sourceModules = listOf(URI("http://not.a.valid.host/bar.pkl")),
settings = settingsModule.toUri(),
allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"),
),
)
val output = evalToConsole(options)
assertThat(output).isEqualTo("foo = 1\n")
}
@Test
fun `eval http module from proxy -- configured in PklProject`(
@TempDir tempDir: Path,
wwRuntimeInfo: WireMockRuntimeInfo
) {
tempDir.writeFile(
"PklProject",
"""
amends "pkl:Project"
evaluatorSettings {
http {
proxy {
address = "${wwRuntimeInfo.httpBaseUrl}"
}
}
}
"""
.trimIndent()
)
stubFor(
get(urlEqualTo("/bar.pkl")).withHost(equalTo("not.a.valid.host")).willReturn(ok("foo = 1"))
)
val options =
CliEvaluatorOptions(
CliBaseOptions(
sourceModules = listOf(URI("http://not.a.valid.host/bar.pkl")),
allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"),
projectDir = tempDir
),
)
val output = evalToConsole(options)
assertThat(output).isEqualTo("foo = 1\n")
}
@Test
fun `eval http module from proxy -- PklProject beats user settings`(
@TempDir tempDir: Path,
wwRuntimeInfo: WireMockRuntimeInfo
) {
val projectDir = tempDir.resolve("my-project")
projectDir.writeFile(
"PklProject",
"""
amends "pkl:Project"
evaluatorSettings {
http {
proxy {
address = "${wwRuntimeInfo.httpBaseUrl}"
}
}
}
"""
.trimIndent()
)
val homeDir = tempDir.resolve("my-home")
homeDir.writeFile(
"settings.pkl",
"""
amends "pkl:settings"
http {
proxy {
address = "http://invalid.proxy.address"
}
}
"""
.trimIndent()
)
val options =
CliEvaluatorOptions(
CliBaseOptions(
sourceModules = listOf(URI("http://not.a.valid.host/bar.pkl")),
allowedModules = SecurityManagers.defaultAllowedModules + Pattern.compile("http:"),
projectDir = projectDir,
settings = homeDir.resolve("settings.pkl").toUri()
),
)
stubFor(get(anyUrl()).willReturn(ok("result = 1")))
val output = evalToConsole(options)
assertThat(output).isEqualTo("result = 1\n")
}
private fun evalModuleThatImportsPackage(certsFile: Path, testPort: Int = -1) {
val moduleUri =
writePklFile(

View File

@@ -6,10 +6,10 @@ com.github.ajalt.clikt:clikt:3.5.1=apiDependenciesMetadata,compileClasspath,defa
com.squareup:javapoet:1.13.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath
io.leangen.geantyref:geantyref:1.3.15=testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.sdk:graal-sdk:23.0.2=default,runtimeClasspath,testRuntimeClasspath
org.graalvm.truffle:truffle-api:23.0.2=default,runtimeClasspath,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -6,10 +6,10 @@ com.github.ajalt.clikt:clikt:3.5.1=apiDependenciesMetadata,compileClasspath,defa
com.squareup:kotlinpoet:1.6.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath
io.leangen.geantyref:geantyref:1.3.15=testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,testRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.sdk:graal-sdk:23.0.2=default,runtimeClasspath,testRuntimeClasspath
org.graalvm.truffle:truffle-api:23.0.2=default,runtimeClasspath,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata

View File

@@ -4,10 +4,10 @@
com.github.ajalt.clikt:clikt-jvm:3.5.1=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.ajalt.clikt:clikt:3.5.1=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.sdk:graal-sdk:23.0.2=default,runtimeClasspath,testRuntimeClasspath
org.graalvm.truffle:truffle-api:23.0.2=default,runtimeClasspath,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -20,7 +20,6 @@ import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration
import java.util.regex.Pattern
import org.pkl.core.http.HttpClient
import org.pkl.core.module.ProjectDependenciesManager
import org.pkl.core.util.IoUtils
@@ -129,6 +128,12 @@ data class CliBaseOptions(
* `~/.pkl/cacerts/` does not exist or is empty, Pkl's built-in CA certificates are used.
*/
val caCertificates: List<Path> = listOf(),
/** The proxy to connect to. */
val proxyAddress: URI? = null,
/** Hostnames, IP addresses, or CIDR blocks to not proxy. */
val noProxy: List<String>? = null,
) {
companion object {
@@ -177,24 +182,4 @@ data class CliBaseOptions(
/** [caCertificates] after normalization. */
val normalizedCaCertificates: List<Path> = caCertificates.map(normalizedWorkingDir::resolve)
/**
* The HTTP client shared between CLI commands created with this [CliBaseOptions] instance.
*
* To release the resources held by the HTTP client in a timely manner, call its `close()` method.
*/
val httpClient: HttpClient by lazy {
with(HttpClient.builder()) {
setTestPort(testPort)
if (normalizedCaCertificates.isEmpty()) {
addDefaultCliCertificates()
} else {
for (file in normalizedCaCertificates) addCertificates(file)
}
// Lazy building significantly reduces execution time of commands that do minimal work.
// However, it means that HTTP client initialization errors won't surface until an HTTP
// request is made.
buildLazily()
}
}
}

View File

@@ -18,6 +18,7 @@ package org.pkl.commons.cli
import java.nio.file.Path
import java.util.regex.Pattern
import org.pkl.core.*
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings
import org.pkl.core.http.HttpClient
import org.pkl.core.module.ModuleKeyFactories
import org.pkl.core.module.ModuleKeyFactory
@@ -35,6 +36,9 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
if (cliOptions.testMode) {
IoUtils.setTestMode()
}
proxyAddress?.let(IoUtils::setSystemProxy)
try {
doRun()
} catch (e: PklException) {
@@ -97,42 +101,44 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
)
}
private val projectSettings: Project.EvaluatorSettings? by lazy {
if (cliOptions.omitProjectSettings) null else project?.settings
private val evaluatorSettings: PklEvaluatorSettings? by lazy {
if (cliOptions.omitProjectSettings) null else project?.evaluatorSettings
}
protected val allowedModules: List<Pattern> by lazy {
cliOptions.allowedModules
?: projectSettings?.allowedModules ?: SecurityManagers.defaultAllowedModules
?: evaluatorSettings?.allowedModules ?: SecurityManagers.defaultAllowedModules
}
protected val allowedResources: List<Pattern> by lazy {
cliOptions.allowedResources
?: projectSettings?.allowedResources ?: SecurityManagers.defaultAllowedResources
?: evaluatorSettings?.allowedResources ?: SecurityManagers.defaultAllowedResources
}
protected val rootDir: Path? by lazy { cliOptions.normalizedRootDir ?: projectSettings?.rootDir }
protected val rootDir: Path? by lazy {
cliOptions.normalizedRootDir ?: evaluatorSettings?.rootDir
}
protected val environmentVariables: Map<String, String> by lazy {
cliOptions.environmentVariables ?: projectSettings?.env ?: System.getenv()
cliOptions.environmentVariables ?: evaluatorSettings?.env ?: System.getenv()
}
protected val externalProperties: Map<String, String> by lazy {
cliOptions.externalProperties ?: projectSettings?.externalProperties ?: emptyMap()
cliOptions.externalProperties ?: evaluatorSettings?.externalProperties ?: emptyMap()
}
protected val moduleCacheDir: Path? by lazy {
if (cliOptions.noCache) null
else
cliOptions.normalizedModuleCacheDir
?: projectSettings?.let { settings ->
if (settings.isNoCache == true) null else settings.moduleCacheDir
?: evaluatorSettings?.let { settings ->
if (settings.noCache == true) null else settings.moduleCacheDir
}
?: IoUtils.getDefaultModuleCacheDir()
}
protected val modulePath: List<Path> by lazy {
cliOptions.normalizedModulePath ?: projectSettings?.modulePath ?: emptyList()
cliOptions.normalizedModulePath ?: evaluatorSettings?.modulePath ?: emptyList()
}
protected val stackFrameTransformer: StackFrameTransformer by lazy {
@@ -152,9 +158,36 @@ abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
)
}
// share HTTP client with other commands with the same cliOptions
protected val httpClient: HttpClient
get() = cliOptions.httpClient
private val proxyAddress =
cliOptions.proxyAddress
?: project?.evaluatorSettings?.http?.proxy?.address ?: settings.http?.proxy?.address
private val noProxy =
cliOptions.noProxy
?: project?.evaluatorSettings?.http?.proxy?.noProxy ?: settings.http?.proxy?.noProxy
/**
* The HTTP client used for this command.
*
* To release resources held by the HTTP client in a timely manner, call [HttpClient.close].
*/
val httpClient: HttpClient by lazy {
with(HttpClient.builder()) {
setTestPort(cliOptions.testPort)
if (cliOptions.normalizedCaCertificates.isEmpty()) {
addDefaultCliCertificates()
} else {
for (file in cliOptions.normalizedCaCertificates) addCertificates(file)
}
if ((proxyAddress ?: noProxy) != null) {
setProxy(proxyAddress, noProxy ?: listOf())
}
// Lazy building significantly reduces execution time of commands that do minimal work.
// However, it means that HTTP client initialization errors won't surface until an HTTP
// request is made.
buildLazily()
}
}
protected fun moduleKeyFactories(modulePathResolver: ModulePathResolver): List<ModuleKeyFactory> {
return buildList {

View File

@@ -27,6 +27,9 @@ fun cliMain(block: () -> Unit) {
if (!message.endsWith('\n')) stream.println()
}
// Force `native-image` to use system proxies (which does not happen with `-D`).
System.setProperty("java.net.useSystemProxies", "true")
try {
block()
} catch (e: CliTestException) {

View File

@@ -172,6 +172,32 @@ class BaseOptions : OptionGroup() {
.path()
.multiple()
@Suppress("HttpUrlsUsage")
val proxy: URI? by
option(
names = arrayOf("--proxy"),
metavar = "<address>",
help = "Proxy to use for HTTP(S) connections."
)
.single()
.convert { URI(it) }
.validate { uri ->
require(
uri.scheme == "http" && uri.host != null && uri.path.isEmpty() && uri.userInfo == null
) {
"Malformed proxy URI (expecting `http://<host>[:<port>]`)"
}
}
val noProxy: List<String>? by
option(
names = arrayOf("--no-proxy"),
metavar = "<pattern1,pattern2>",
help = "Hostnames that should not be connected to via a proxy."
)
.single()
.split(",")
// hidden option used by native tests
private val testPort: Int by
option(names = arrayOf("--test-port"), help = "Internal test option", hidden = true)
@@ -202,7 +228,9 @@ class BaseOptions : OptionGroup() {
testPort = testPort,
omitProjectSettings = projectOptions?.omitProjectSettings ?: false,
noProject = projectOptions?.noProject ?: false,
caCertificates = caCertificates
caCertificates = caCertificates,
proxyAddress = proxy,
noProxy = noProxy ?: emptyList()
)
}
}

View File

@@ -2,11 +2,11 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
net.bytebuddy:byte-buddy:1.12.21=default
net.bytebuddy:byte-buddy:1.14.11=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=apiDependenciesMetadata,compileClasspath,implementationDependenciesMetadata,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.24.2=default
org.assertj:assertj-core:3.25.3=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -1,10 +1,10 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -8,10 +8,10 @@ com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,pklCodegenJava,runtimeClasspat
io.leangen.geantyref:geantyref:1.3.14=default
io.leangen.geantyref:geantyref:1.3.15=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
javax.inject:javax.inject:1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.sdk:graal-sdk:23.0.2=default,pklCodegenJava,runtimeClasspath,testRuntimeClasspath
org.graalvm.truffle:truffle-api:23.0.2=default,pklCodegenJava,runtimeClasspath,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -7,10 +7,10 @@ com.squareup:kotlinpoet:1.6.0=pklCodegenKotlin
com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,pklCodegenKotlin,pklConfigJava,runtimeClasspath,testRuntimeClasspath
io.leangen.geantyref:geantyref:1.3.14=default
io.leangen.geantyref:geantyref:1.3.15=pklConfigJava,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.sdk:graal-sdk:23.0.2=default,pklCodegenKotlin,pklConfigJava,runtimeClasspath,testRuntimeClasspath
org.graalvm.truffle:truffle-api:23.0.2=default,pklCodegenKotlin,pklConfigJava,runtimeClasspath,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -6,13 +6,13 @@ com.squareup:javapoet:1.13.0=generatorCompileClasspath,generatorImplementationDe
com.tunnelvisionlabs:antlr4-annotations:4.9.0=antlr
com.tunnelvisionlabs:antlr4-runtime:4.9.0=antlr,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.tunnelvisionlabs:antlr4:4.9.0=antlr
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.abego.treelayout:org.abego.treelayout.core:1.0.1=antlr
org.antlr:ST4:4.3=antlr
org.antlr:antlr-runtime:3.5.2=antlr
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.sdk:graal-sdk:23.0.2=compileClasspath,default,generatorCompileClasspath,generatorImplementationDependenciesMetadata,generatorRuntimeClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.truffle:truffle-api:23.0.2=compileClasspath,default,generatorCompileClasspath,generatorImplementationDependenciesMetadata,generatorRuntimeClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.truffle:truffle-dsl-processor:23.0.2=annotationProcessor

View File

@@ -440,39 +440,39 @@ public final class EvaluatorBuilder {
*/
public EvaluatorBuilder applyFromProject(Project project) {
this.dependencies = project.getDependencies();
var settings = project.getSettings();
var settings = project.getEvaluatorSettings();
if (securityManager != null) {
throw new IllegalStateException(
"Cannot call both `setSecurityManager` and `setProject`, because both define security manager settings. Call `setProjectOnly` if the security manager is desired.");
}
if (settings.getAllowedModules() != null) {
setAllowedModules(settings.getAllowedModules());
if (settings.allowedModules() != null) {
setAllowedModules(settings.allowedModules());
}
if (settings.getAllowedResources() != null) {
setAllowedResources(settings.getAllowedResources());
if (settings.allowedResources() != null) {
setAllowedResources(settings.allowedResources());
}
if (settings.getExternalProperties() != null) {
setExternalProperties(settings.getExternalProperties());
if (settings.externalProperties() != null) {
setExternalProperties(settings.externalProperties());
}
if (settings.getEnv() != null) {
setEnvironmentVariables(settings.getEnv());
if (settings.env() != null) {
setEnvironmentVariables(settings.env());
}
if (settings.getTimeout() != null) {
setTimeout(settings.getTimeout().toJavaDuration());
if (settings.timeout() != null) {
setTimeout(settings.timeout().toJavaDuration());
}
if (settings.getModulePath() != null) {
if (settings.modulePath() != null) {
// indirectly closed by `ModuleKeyFactories.closeQuietly(builder.moduleKeyFactories)`
var modulePathResolver = new ModulePathResolver(settings.getModulePath());
var modulePathResolver = new ModulePathResolver(settings.modulePath());
addResourceReader(ResourceReaders.modulePath(modulePathResolver));
addModuleKeyFactory(ModuleKeyFactories.modulePath(modulePathResolver));
}
if (settings.getRootDir() != null) {
setRootDir(settings.getRootDir());
if (settings.rootDir() != null) {
setRootDir(settings.rootDir());
}
if (Boolean.TRUE.equals(settings.isNoCache())) {
if (Boolean.TRUE.equals(settings.noCache())) {
setModuleCacheDir(null);
} else if (settings.getModuleCacheDir() != null) {
setModuleCacheDir(settings.getModuleCacheDir());
} else if (settings.moduleCacheDir() != null) {
setModuleCacheDir(settings.moduleCacheDir());
}
return this;
}

View File

@@ -108,7 +108,7 @@ public final class StackFrameTransformers {
public static StackFrameTransformer createDefault(PklSettings settings) {
return defaultTransformer
// order is relevant
.andThen(convertFilePathToUriScheme(settings.getEditor().getUrlScheme()));
.andThen(convertFilePathToUriScheme(settings.editor().urlScheme()));
}
private static StackFrameTransformer loadFromServiceProviders() {

View File

@@ -0,0 +1,191 @@
/**
* Copyright © 2024 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.
* You may obtain a copy of the License at
*
* https://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 org.pkl.core.evaluatorSettings;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
import org.pkl.core.Duration;
import org.pkl.core.PNull;
import org.pkl.core.PObject;
import org.pkl.core.PklBugException;
import org.pkl.core.PklException;
import org.pkl.core.Value;
import org.pkl.core.util.ErrorMessages;
import org.pkl.core.util.Nullable;
/** Java version of {@code pkl.EvaluatorSettings}. */
public record PklEvaluatorSettings(
@Nullable Map<String, String> externalProperties,
@Nullable Map<String, String> env,
@Nullable List<Pattern> allowedModules,
@Nullable List<Pattern> allowedResources,
@Nullable Boolean noCache,
@Nullable Path moduleCacheDir,
@Nullable List<Path> modulePath,
@Nullable Duration timeout,
@Nullable Path rootDir,
@Nullable Http http) {
/** Initializes a {@link PklEvaluatorSettings} from a raw object representation. */
@SuppressWarnings("unchecked")
public static PklEvaluatorSettings parse(
Value input, BiFunction<? super String, ? super String, Path> pathNormalizer) {
if (!(input instanceof PObject pSettings)) {
throw PklBugException.unreachableCode();
}
var moduleCacheDirStr = (String) pSettings.get("moduleCacheDir");
var moduleCacheDir =
moduleCacheDirStr == null
? null
: pathNormalizer.apply(moduleCacheDirStr, "moduleCacheDir");
var allowedModulesStrs = (List<String>) pSettings.get("allowedModules");
var allowedModules =
allowedModulesStrs == null
? null
: allowedModulesStrs.stream().map(Pattern::compile).toList();
var allowedResourcesStrs = (List<String>) pSettings.get("allowedResources");
var allowedResources =
allowedResourcesStrs == null
? null
: allowedResourcesStrs.stream().map(Pattern::compile).toList();
var modulePathStrs = (List<String>) pSettings.get("modulePath");
var modulePath =
modulePathStrs == null
? null
: modulePathStrs.stream().map(it -> pathNormalizer.apply(it, "modulePath")).toList();
var rootDirStr = (String) pSettings.get("rootDir");
var rootDir = rootDirStr == null ? null : pathNormalizer.apply(rootDirStr, "rootDir");
return new PklEvaluatorSettings(
(Map<String, String>) pSettings.get("externalProperties"),
(Map<String, String>) pSettings.get("env"),
allowedModules,
allowedResources,
(Boolean) pSettings.get("noCache"),
moduleCacheDir,
modulePath,
(Duration) pSettings.get("timeout"),
rootDir,
Http.parse((Value) pSettings.get("http")));
}
public record Http(@Nullable Proxy proxy) {
public static final Http DEFAULT = new Http(null);
public static @Nullable Http parse(@Nullable Value input) {
if (input == null || input instanceof PNull) {
return null;
} else if (input instanceof PObject http) {
var proxy = Proxy.parse((Value) http.getProperty("proxy"));
return proxy == null ? DEFAULT : new Http(proxy);
} else {
throw PklBugException.unreachableCode();
}
}
}
public record Proxy(@Nullable URI address, @Nullable List<String> noProxy) {
public static Proxy create(@Nullable String address, @Nullable List<String> noProxy) {
URI addressUri;
try {
addressUri = address == null ? null : new URI(address);
} catch (URISyntaxException e) {
throw new PklException(ErrorMessages.create("invalidUri", address));
}
return new Proxy(addressUri, noProxy);
}
@SuppressWarnings("unchecked")
public static @Nullable Proxy parse(Value input) {
if (input instanceof PNull) {
return null;
} else if (input instanceof PObject proxy) {
var address = (String) proxy.get("address");
var noProxy = (List<String>) proxy.get("noProxy");
return create(address, noProxy);
} else {
throw PklBugException.unreachableCode();
}
}
}
private boolean arePatternsEqual(
@Nullable List<Pattern> thesePatterns, @Nullable List<Pattern> thosePatterns) {
if (thesePatterns == null) {
return thosePatterns == null;
}
if (thosePatterns == null || thesePatterns.size() != thosePatterns.size()) {
return false;
}
for (var i = 0; i < thesePatterns.size(); i++) {
if (!thesePatterns.get(i).pattern().equals(thosePatterns.get(i).pattern())) {
return false;
}
}
return true;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof PklEvaluatorSettings that)) {
return false;
}
return Objects.equals(externalProperties, that.externalProperties)
&& Objects.equals(env, that.env)
&& arePatternsEqual(allowedModules, that.allowedModules)
&& arePatternsEqual(allowedResources, that.allowedResources)
&& Objects.equals(noCache, that.noCache)
&& Objects.equals(moduleCacheDir, that.moduleCacheDir)
&& Objects.equals(timeout, that.timeout)
&& Objects.equals(rootDir, that.rootDir)
&& Objects.equals(http, that.http);
}
private int hashPatterns(@Nullable List<Pattern> patterns) {
if (patterns == null) {
return 0;
}
var ret = 1;
for (var pattern : patterns) {
ret = 31 * ret + pattern.pattern().hashCode();
}
return ret;
}
@Override
public int hashCode() {
var result =
Objects.hash(externalProperties, env, noCache, moduleCacheDir, timeout, rootDir, http);
result = 31 * result + hashPatterns(allowedModules);
result = 31 * result + hashPatterns(allowedResources);
return result;
}
}

View File

@@ -0,0 +1,4 @@
@NonnullByDefault
package org.pkl.core.evaluatorSettings;
import org.pkl.core.util.NonnullByDefault;

View File

@@ -21,7 +21,9 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.nio.file.Path;
import java.util.List;
import javax.net.ssl.SSLContext;
import org.pkl.core.util.Nullable;
/**
* An HTTP client.
@@ -36,6 +38,7 @@ import javax.net.ssl.SSLContext;
public interface HttpClient extends AutoCloseable {
/** A builder of {@linkplain HttpClient HTTP clients}. */
@SuppressWarnings("unused")
interface Builder {
/**
* Sets the {@code User-Agent} header.
@@ -116,6 +119,32 @@ public interface HttpClient extends AutoCloseable {
*/
Builder setTestPort(int port);
/**
* Sets the proxy selector to use when establishing connections.
*
* <p>Defaults to: {@link java.net.ProxySelector#getDefault()}.
*/
Builder setProxySelector(java.net.ProxySelector proxySelector);
/**
* Configures HTTP connections to connect to the provided proxy address.
*
* <p>The provided {@code proxyAddress} must have scheme http, not contain userInfo, and not
* have a path segment.
*
* <p>If {@code proxyAddress} is {@code null}, uses the proxy address provided by {@link
* java.net.ProxySelector#getDefault()}.
*
* <p>NOTE: Due to a <a href="https://bugs.openjdk.org/browse/JDK-8256409">limitation in the
* JDK</a>, this does not configure the proxy server used for certificate revocation checking.
* To configure the certificate revocation checker, the result of {@link
* java.net.ProxySelector#getDefault} needs to be changed either by setting system properties,
* or via {@link java.net.ProxySelector#setDefault}.
*
* @throws IllegalArgumentException if `proxyAddress` is invalid.
*/
Builder setProxy(@Nullable URI proxyAddress, List<String> noProxy);
/**
* Creates a new {@code HttpClient} from the current state of this builder.
*

View File

@@ -16,6 +16,7 @@
package org.pkl.core.http;
import java.io.IOException;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
@@ -25,6 +26,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.pkl.core.Release;
import org.pkl.core.http.HttpClient.Builder;
import org.pkl.core.util.ErrorMessages;
import org.pkl.core.util.IoUtils;
@@ -36,6 +38,7 @@ final class HttpClientBuilder implements HttpClient.Builder {
private final List<Path> certificateFiles = new ArrayList<>();
private final List<URI> certificateUris = new ArrayList<>();
private int testPort = -1;
private ProxySelector proxySelector;
HttpClientBuilder() {
this(IoUtils.getPklHomeDir().resolve("cacerts"));
@@ -109,6 +112,17 @@ final class HttpClientBuilder implements HttpClient.Builder {
return this;
}
public HttpClient.Builder setProxySelector(ProxySelector proxySelector) {
this.proxySelector = proxySelector;
return this;
}
@Override
public Builder setProxy(URI proxyAddress, List<String> noProxy) {
this.proxySelector = new org.pkl.core.http.ProxySelector(proxyAddress, noProxy);
return this;
}
@Override
public HttpClient build() {
return doBuild().get();
@@ -123,8 +137,11 @@ final class HttpClientBuilder implements HttpClient.Builder {
// make defensive copies because Supplier may get called after builder was mutated
var certificateFiles = List.copyOf(this.certificateFiles);
var certificateUris = List.copyOf(this.certificateUris);
var proxySelector =
this.proxySelector != null ? this.proxySelector : java.net.ProxySelector.getDefault();
return () -> {
var jdkClient = new JdkHttpClient(certificateFiles, certificateUris, connectTimeout);
var jdkClient =
new JdkHttpClient(certificateFiles, certificateUris, connectTimeout, proxySelector);
return new RequestRewritingClient(userAgent, requestTimeout, testPort, jdkClient);
};
}

View File

@@ -77,11 +77,16 @@ final class JdkHttpClient implements HttpClient {
closeMethod = result;
}
JdkHttpClient(List<Path> certificateFiles, List<URI> certificateUris, Duration connectTimeout) {
JdkHttpClient(
List<Path> certificateFiles,
List<URI> certificateUris,
Duration connectTimeout,
java.net.ProxySelector proxySelector) {
underlying =
java.net.http.HttpClient.newBuilder()
.sslContext(createSslContext(certificateFiles, certificateUris))
.connectTimeout(connectTimeout)
.proxy(proxySelector)
.followRedirects(Redirect.NORMAL)
.build();
}

View File

@@ -0,0 +1,215 @@
/**
* Copyright © 2024 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.
* You may obtain a copy of the License at
*
* https://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 org.pkl.core.http;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.regex.Pattern;
import org.pkl.core.util.Nullable;
/**
* Represents a noproxy entry.
*
* <p>Follows the rules described in <a
* href="https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/#standardizing-no_proxy">Standardizing
* {@code no_proxy}</a>
*/
final class NoProxyRule {
private static final String portString = "(?::(?<port>\\d{1,5}))?";
private static final String cidrString = "(?:/(?<cidr>\\d{1,3}))?";
private static final String ipv4AddressString =
"(?<host>[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})";
private static final Pattern ipv4Address = Pattern.compile("^" + ipv4AddressString + "$");
private static final Pattern ipv4AddressOrCidr =
Pattern.compile("^" + ipv4AddressString + cidrString + portString + "$");
private static final String ipv6AddressString =
"(?<host>(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(?:ffff(:0{1,4})?:)?(?:(?:25[0-5]|(2[0-4]|1?[0-9])?[0-9])\\.){3}(?:25[0-5]|(?:2[0-4]|1?[0-9])?[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?[0-9])?[0-9])\\.){3}(?:25[0-5]|(?:2[0-4]|1?[0-9])?[0-9]))";
private static final Pattern ipv6AddressOrCidr =
Pattern.compile(
"^(?<open>\\[)?" + ipv6AddressString + cidrString + "(?<close>])?" + portString + "$");
private static final Pattern hostnamePattern =
Pattern.compile("^\\.?(?<host>[^:]+)" + portString + "$");
private @Nullable Integer ipv4 = null;
private @Nullable Integer ipv4Mask = null;
private @Nullable BigInteger ipv6 = null;
private @Nullable BigInteger ipv6Mask = null;
private @Nullable String hostname = null;
private int port = 0;
private boolean allNoProxy = false;
public NoProxyRule(String repr) {
if (repr.equals("*")) {
allNoProxy = true;
return;
}
var ipv4Matcher = ipv4AddressOrCidr.matcher(repr);
if (ipv4Matcher.matches()) {
var ipAddress = ipv4Matcher.group("host");
ipv4 = parseIpv4(ipAddress);
if (ipv4Matcher.group("cidr") != null) {
var prefixLength = Integer.parseInt(ipv4Matcher.group("cidr"));
if (prefixLength > 32) {
// best-effort (don't fail on invalid cidrs).
hostname = repr;
}
ipv4Mask = 0xffffffff << (32 - prefixLength);
}
if (ipv4Matcher.group("port") != null) {
port = Integer.parseInt(ipv4Matcher.group("port"));
}
return;
}
var ipv6Matcher = ipv6AddressOrCidr.matcher(repr);
if (ipv6Matcher.matches()) {
var ipAddress = ipv6Matcher.group("host");
ipv6 = parseIpv6(ipAddress);
if (ipv6Matcher.group("cidr") != null) {
var maskBuffer = ByteBuffer.allocate(16).putLong(-1L).putLong(-1L);
var prefixLength = Integer.parseInt(ipv6Matcher.group("cidr"));
if (prefixLength > 128) {
// best-effort (don't fail on invalid cidrs).
hostname = repr;
return;
}
ipv6Mask = new BigInteger(1, maskBuffer.array()).not().shiftRight(prefixLength);
}
if (ipv6Matcher.group("port") != null) {
port = Integer.parseInt(ipv6Matcher.group("port"));
}
return;
}
var hostnameMatcher = hostnamePattern.matcher(repr);
if (hostnameMatcher.matches()) {
hostname = hostnameMatcher.group("host");
if (hostnameMatcher.group("port") != null) {
port = Integer.parseInt(hostnameMatcher.group("port"));
}
return;
}
throw new RuntimeException("Failed to parse hostname in no-proxy rule: " + repr);
}
public boolean matches(URI uri) {
if (allNoProxy) {
return true;
}
if (!hostMatches(uri)) {
return false;
}
if (port == 0) {
return true;
}
var thatPort = uri.getPort();
if (thatPort == -1) {
thatPort =
switch (uri.getScheme()) {
case "http" -> 80;
case "https" -> 443;
default -> -1;
};
}
return port == thatPort;
}
/** Tells if the provided URI should not be proxied according to the rules described. */
public boolean hostMatches(URI uri) {
if (allNoProxy) {
return true;
}
var host = uri.getHost();
if (host == null) {
return false;
}
if (host.equalsIgnoreCase(hostname)) {
return true;
}
if (hostname != null && endsWithIgnoreCase(host, "." + hostname)) {
return true;
}
return ipV6Matches(uri.getHost()) || ipV4Matches(uri.getHost());
}
private boolean endsWithIgnoreCase(String str, String suffix) {
var len = suffix.length();
return str.regionMatches(true, str.length() - len, suffix, 0, len);
}
private boolean ipV4Matches(String hostname) {
if (ipv4 == null) {
return false;
}
if (!ipv4Address.matcher(hostname).matches()) {
return false;
}
var address = parseIpv4(hostname);
if (ipv4.equals(address)) {
return true;
}
if (ipv4Mask != null) {
return (ipv4 & ipv4Mask) == (address & ipv4Mask);
}
return false;
}
private boolean ipV6Matches(String hostname) {
if (ipv6 == null) {
return false;
}
if (!hostname.startsWith("[") && !hostname.endsWith("]")) {
return false;
}
var ipv6Repr = hostname.substring(1, hostname.length() - 1);
// According to RFC3986, square brackets can _only_ surround IPV6 addresses, so it should be
// safe to straight up parse it.
// <https://www.ietf.org/rfc/rfc3986.txt>
var address = parseIpv6(ipv6Repr);
if (ipv6.equals(address)) {
return true;
}
if (ipv6Mask != null) {
return ipv6.and(ipv6Mask).equals(address.and(ipv6Mask));
}
return false;
}
private BigInteger parseIpv6(String repr) {
try {
var inet = Inet6Address.getByName(repr);
var byteArr = inet.getAddress();
return new BigInteger(1, byteArr);
} catch (UnknownHostException e) {
// should never happen; `repr` is an IPV6 literal.
throw new RuntimeException(
"Received unexpected UnknownHostException during parsing IPV6 literal", e);
}
}
private int parseIpv4(String repr) {
try {
var inet = Inet4Address.getByName(repr);
return ByteBuffer.wrap(inet.getAddress()).getInt();
} catch (UnknownHostException e) {
// should never happen; `repr` is an IPV4 literal.
throw new RuntimeException(
"Received unexpected UnknownHostException during parsing IPV4 literal", e);
}
}
}

View File

@@ -0,0 +1,78 @@
/**
* Copyright © 2024 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.
* You may obtain a copy of the License at
*
* https://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 org.pkl.core.http;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URI;
import java.util.List;
import org.pkl.core.util.ErrorMessages;
import org.pkl.core.util.Nullable;
final class ProxySelector extends java.net.ProxySelector {
public static final List<Proxy> NO_PROXY = List.of(Proxy.NO_PROXY);
private final @Nullable List<Proxy> myProxy;
private final List<NoProxyRule> noProxyRules;
private final @Nullable java.net.ProxySelector delegate;
ProxySelector(@Nullable URI proxyAddress, List<String> noProxyRules) {
this.noProxyRules = noProxyRules.stream().map(NoProxyRule::new).toList();
if (proxyAddress == null) {
this.delegate = java.net.ProxySelector.getDefault();
this.myProxy = null;
} else {
if (!proxyAddress.getScheme().equalsIgnoreCase("http")
|| proxyAddress.getHost() == null
|| !proxyAddress.getPath().isEmpty()
|| proxyAddress.getUserInfo() != null) {
throw new IllegalArgumentException(
ErrorMessages.create("malformedProxyAddress", proxyAddress));
}
this.delegate = null;
var port = proxyAddress.getPort();
if (port == -1) {
port = 80;
}
this.myProxy =
List.of(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAddress.getHost(), port)));
}
}
@Override
@ExplodeLoop
public List<Proxy> select(URI uri) {
for (var proxyRule : noProxyRules) {
if (proxyRule.matches(uri)) {
return NO_PROXY;
}
}
if (delegate != null) {
return delegate.select(uri);
}
assert myProxy != null;
return myProxy;
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
/* ignore */
}
}

View File

@@ -39,7 +39,9 @@ import org.pkl.core.SecurityManager;
import org.pkl.core.SecurityManagers;
import org.pkl.core.StackFrameTransformer;
import org.pkl.core.StackFrameTransformers;
import org.pkl.core.Value;
import org.pkl.core.Version;
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings;
import org.pkl.core.module.ModuleKeyFactories;
import org.pkl.core.packages.Checksums;
import org.pkl.core.packages.Dependency.RemoteDependency;
@@ -54,7 +56,7 @@ import org.pkl.core.util.Nullable;
public final class Project {
private final @Nullable Package pkg;
private final DeclaredDependencies dependencies;
private final EvaluatorSettings evaluatorSettings;
private final PklEvaluatorSettings evaluatorSettings;
private final URI projectFileUri;
private final URI projectBaseUri;
private final List<URI> tests;
@@ -178,7 +180,9 @@ public final class Project {
getProperty(
module,
"evaluatorSettings",
(settings) -> parseEvaluatorSettings(settings, projectBaseUri));
(settings) ->
PklEvaluatorSettings.parse(
(Value) settings, (it, name) -> resolveNullablePath(it, projectBaseUri, name)));
@SuppressWarnings("unchecked")
var testPathStrs = (List<String>) getProperty(module, "tests");
var tests =
@@ -210,51 +214,6 @@ public final class Project {
return result;
}
@SuppressWarnings("unchecked")
private static EvaluatorSettings parseEvaluatorSettings(Object settings, URI projectBaseUri) {
var pSettings = (PObject) settings;
var externalProperties = getNullableProperty(pSettings, "externalProperties", Project::asMap);
var env = getNullableProperty(pSettings, "env", Project::asMap);
var allowedModules = getNullableProperty(pSettings, "allowedModules", Project::asPatternList);
var allowedResources =
getNullableProperty(pSettings, "allowedResources", Project::asPatternList);
var noCache = (Boolean) getNullableProperty(pSettings, "noCache");
var modulePathStrs = (List<String>) getNullableProperty(pSettings, "modulePath");
var timeout = (Duration) getNullableProperty(pSettings, "timeout");
List<Path> modulePath = null;
if (modulePathStrs != null) {
modulePath =
modulePathStrs.stream()
.map((it) -> resolveNullablePath(it, projectBaseUri, "modulePath"))
.collect(Collectors.toList());
}
var moduleCacheDir = getNullablePath(pSettings, "moduleCacheDir", projectBaseUri);
var rootDir = getNullablePath(pSettings, "rootDir", projectBaseUri);
return new EvaluatorSettings(
externalProperties,
env,
allowedModules,
allowedResources,
noCache,
moduleCacheDir,
modulePath,
timeout,
rootDir);
}
@SuppressWarnings("unchecked")
private static Map<String, String> asMap(Object t) {
assert t instanceof Map;
return (Map<String, String>) t;
}
@SuppressWarnings("unchecked")
private static List<Pattern> asPatternList(Object t) {
return ((List<String>) t).stream().map(Pattern::compile).collect(Collectors.toList());
}
private static Object getProperty(PObject settings, String propertyName) {
return settings.getProperty(propertyName);
}
@@ -307,12 +266,6 @@ public final class Project {
}
}
private static @Nullable Path getNullablePath(
Composite object, String propertyName, URI projectBaseUri) {
return resolveNullablePath(
(String) getNullableProperty(object, propertyName), projectBaseUri, propertyName);
}
@SuppressWarnings("unchecked")
private static Package parsePackage(PObject pObj) throws URISyntaxException {
var name = (String) pObj.getProperty("name");
@@ -353,7 +306,7 @@ public final class Project {
private Project(
@Nullable Package pkg,
DeclaredDependencies dependencies,
EvaluatorSettings evaluatorSettings,
PklEvaluatorSettings evaluatorSettings,
URI projectFileUri,
URI projectBaseUri,
List<URI> tests,
@@ -371,7 +324,13 @@ public final class Project {
return pkg;
}
/** Use {@link org.pkl.core.project.Project#getEvaluatorSettings()} instead. */
@Deprecated(forRemoval = true)
public EvaluatorSettings getSettings() {
return new EvaluatorSettings(evaluatorSettings);
}
public PklEvaluatorSettings getEvaluatorSettings() {
return evaluatorSettings;
}
@@ -430,17 +389,13 @@ public final class Project {
return Path.of(projectBaseUri);
}
@Deprecated(forRemoval = true)
public static class EvaluatorSettings {
private final PklEvaluatorSettings delegate;
private final @Nullable Map<String, String> externalProperties;
private final @Nullable Map<String, String> env;
private final @Nullable List<Pattern> allowedModules;
private final @Nullable List<Pattern> allowedResources;
private final @Nullable Boolean noCache;
private final @Nullable Path moduleCacheDir;
private final @Nullable List<Path> modulePath;
private final @Nullable Duration timeout;
private final @Nullable Path rootDir;
public EvaluatorSettings(PklEvaluatorSettings delegate) {
this.delegate = delegate;
}
public EvaluatorSettings(
@Nullable Map<String, String> externalProperties,
@@ -452,81 +407,63 @@ public final class Project {
@Nullable List<Path> modulePath,
@Nullable Duration timeout,
@Nullable Path rootDir) {
this.externalProperties = externalProperties;
this.env = env;
this.allowedModules = allowedModules;
this.allowedResources = allowedResources;
this.noCache = noCache;
this.moduleCacheDir = moduleCacheDir;
this.modulePath = modulePath;
this.timeout = timeout;
this.rootDir = rootDir;
this.delegate =
new PklEvaluatorSettings(
externalProperties,
env,
allowedModules,
allowedResources,
noCache,
moduleCacheDir,
modulePath,
timeout,
rootDir,
null);
}
@Deprecated(forRemoval = true)
public @Nullable Map<String, String> getExternalProperties() {
return externalProperties;
return delegate.externalProperties();
}
@Deprecated(forRemoval = true)
public @Nullable Map<String, String> getEnv() {
return env;
return delegate.env();
}
@Deprecated(forRemoval = true)
public @Nullable List<Pattern> getAllowedModules() {
return allowedModules;
return delegate.allowedModules();
}
@Deprecated(forRemoval = true)
public @Nullable List<Pattern> getAllowedResources() {
return allowedResources;
return delegate.allowedResources();
}
@Deprecated(forRemoval = true)
public @Nullable Boolean isNoCache() {
return noCache;
return delegate.noCache();
}
@Deprecated(forRemoval = true)
public @Nullable List<Path> getModulePath() {
return modulePath;
return delegate.modulePath();
}
@Deprecated(forRemoval = true)
public @Nullable Duration getTimeout() {
return timeout;
return delegate.timeout();
}
@Deprecated(forRemoval = true)
public @Nullable Path getModuleCacheDir() {
return moduleCacheDir;
return delegate.moduleCacheDir();
}
@Deprecated(forRemoval = true)
public @Nullable Path getRootDir() {
return rootDir;
}
private boolean arePatternsEqual(
@Nullable List<Pattern> myPattern, @Nullable List<Pattern> thatPattern) {
if (myPattern == null) {
return thatPattern == null;
}
if (thatPattern == null) {
return false;
}
if (myPattern.size() != thatPattern.size()) {
return false;
}
for (var i = 0; i < myPattern.size(); i++) {
if (!myPattern.get(i).pattern().equals(thatPattern.get(i).pattern())) {
return false;
}
}
return true;
}
private int hashPatterns(@Nullable List<Pattern> patterns) {
if (patterns == null) {
return 0;
}
var ret = 1;
for (var pattern : patterns) {
ret = 31 * ret + pattern.pattern().hashCode();
}
return ret;
return delegate.rootDir();
}
@Override
@@ -534,52 +471,37 @@ public final class Project {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EvaluatorSettings that = (EvaluatorSettings) o;
return Objects.equals(externalProperties, that.externalProperties)
&& Objects.equals(env, that.env)
&& arePatternsEqual(allowedModules, that.allowedModules)
&& arePatternsEqual(allowedResources, that.allowedResources)
&& Objects.equals(noCache, that.noCache)
&& Objects.equals(moduleCacheDir, that.moduleCacheDir)
&& Objects.equals(modulePath, that.modulePath)
&& Objects.equals(timeout, that.timeout)
&& Objects.equals(rootDir, that.rootDir);
return o != null
&& getClass() == o.getClass()
&& Objects.equals(delegate, ((EvaluatorSettings) o).delegate);
}
@Override
public int hashCode() {
var result =
Objects.hash(
externalProperties, env, noCache, moduleCacheDir, modulePath, timeout, rootDir);
result = 31 * result + hashPatterns(allowedModules);
result = 31 * result + hashPatterns(allowedResources);
return result;
return delegate.hashCode();
}
@Override
public String toString() {
return "EvaluatorSettings{"
+ "externalProperties="
+ externalProperties
+ delegate.externalProperties()
+ ", env="
+ env
+ delegate.env()
+ ", allowedModules="
+ allowedModules
+ delegate.allowedModules()
+ ", allowedResources="
+ allowedResources
+ delegate.allowedResources()
+ ", noCache="
+ noCache
+ delegate.noCache()
+ ", moduleCacheDir="
+ moduleCacheDir
+ delegate.moduleCacheDir()
+ ", modulePath="
+ modulePath
+ delegate.modulePath()
+ ", timeout="
+ timeout
+ delegate.timeout()
+ ", rootDir="
+ rootDir
+ delegate.rootDir()
+ '}';
}
}

View File

@@ -20,9 +20,11 @@ import java.nio.file.Path;
import java.util.List;
import java.util.regex.Pattern;
import org.pkl.core.*;
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings;
import org.pkl.core.module.ModuleKeyFactories;
import org.pkl.core.resource.ResourceReaders;
import org.pkl.core.runtime.VmEvalException;
import org.pkl.core.runtime.VmExceptionBuilder;
import org.pkl.core.util.IoUtils;
import org.pkl.core.util.Nullable;
@@ -32,19 +34,13 @@ import org.pkl.core.util.Nullable;
* {@code load} methods.
*/
// keep in sync with stdlib/settings.pkl
public final class PklSettings {
public record PklSettings(Editor editor, @Nullable PklEvaluatorSettings.Http http) {
private static final List<Pattern> ALLOWED_MODULES =
List.of(Pattern.compile("pkl:"), Pattern.compile("file:"));
private static final List<Pattern> ALLOWED_RESOURCES =
List.of(Pattern.compile("env:"), Pattern.compile("file:"));
private final Editor editor;
public PklSettings(Editor editor) {
this.editor = editor;
}
/**
* Loads the user settings file ({@literal ~/.pkl/settings.pkl}). If this file does not exist,
* returns default settings defined by module {@literal pkl.settings}.
@@ -56,7 +52,9 @@ public final class PklSettings {
/** For testing only. */
static PklSettings loadFromPklHomeDir(Path pklHomeDir) throws VmEvalException {
var path = pklHomeDir.resolve("settings.pkl");
return Files.exists(path) ? load(ModuleSource.path(path)) : new PklSettings(Editor.SYSTEM);
return Files.exists(path)
? load(ModuleSource.path(path))
: new PklSettings(Editor.SYSTEM, null);
}
/** Loads a settings file from the given path. */
@@ -73,46 +71,34 @@ public final class PklSettings {
.addEnvironmentVariables(System.getenv())
.build()) {
var module = evaluator.evaluateOutputValueAs(moduleSource, PClassInfo.Settings);
return parseSettings(module);
return parseSettings(module, moduleSource);
}
}
private static PklSettings parseSettings(PObject module) throws VmEvalException {
// can't use object mapping in pkl-core, so map manually
var editor = (PObject) module.getProperty("editor");
var urlScheme = (String) editor.getProperty("urlScheme");
return new PklSettings(new Editor(urlScheme));
private static PklSettings parseSettings(PObject module, ModuleSource location)
throws VmEvalException {
if (!(module.getPropertyOrNull("editor") instanceof PObject pObject)
|| !(pObject.getPropertyOrNull("urlScheme") instanceof String str)) {
throw new VmExceptionBuilder().evalError("invalidSettingsFile", location.getUri()).build();
}
var editor = new Editor(str);
var httpSettings = PklEvaluatorSettings.Http.parse((Value) module.getProperty("http"));
return new PklSettings(editor, httpSettings);
}
/** Returns the editor for viewing and editing Pkl files. */
/**
* Returns the editor for viewing and editing Pkl files.
*
* <p>This method is deprecated, use {@link #editor()} instead.
*/
@Deprecated(forRemoval = true)
public Editor getEditor() {
return editor;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
var that = (PklSettings) o;
return editor.equals(that.editor);
}
@Override
public int hashCode() {
return editor.hashCode();
}
@Override
public String toString() {
return "PklSettings{" + "editor=" + editor + '}';
}
/** An editor for viewing and editing Pkl files. */
public static final class Editor {
private final String urlScheme;
public record Editor(String urlScheme) {
/** The editor associated with {@code file:} URLs ending in {@code .pkl}. */
public static final Editor SYSTEM = new Editor("%{url}, line %{line}");
@@ -134,37 +120,15 @@ public final class PklSettings {
/** The <a href="https://code.visualstudio.com">Visual Studio Code</a> editor. */
public static final Editor VS_CODE = new Editor("vscode://file/%{path}:%{line}:%{column}");
/** Constructs an editor. */
public Editor(String urlScheme) {
this.urlScheme = urlScheme;
}
/**
* Returns the URL scheme for opening files in this editor. The following placeholders are
* supported: {@code %{url}}, {@code %{path}}, {@code %{line}}, {@code %{column}}.
*
* <p>This method is deprecated; use {@link #urlScheme()} instead.
*/
@Deprecated(forRemoval = true)
public String getUrlScheme() {
return urlScheme;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
var editor = (Editor) o;
return urlScheme.equals(editor.urlScheme);
}
@Override
public int hashCode() {
return urlScheme.hashCode();
}
@Override
public String toString() {
return "Editor{" + "urlScheme='" + urlScheme + '\'' + '}';
}
}
}

View File

@@ -540,6 +540,18 @@ public final class IoUtils {
System.setProperty("org.pkl.testMode", "true");
}
public static void setSystemProxy(URI proxyAddress) {
// Set HTTP proxy settings to configure the certificate revocation checker, because
// there is no other way to configure it. (see https://bugs.openjdk.org/browse/JDK-8256409)
//
// This only influences the behavior of the revocation checker.
// Otherwise, proxying is handled by [ProxySelector].
System.setProperty("http.proxyHost", proxyAddress.getHost());
System.setProperty(
"http.proxyPort",
proxyAddress.getPort() == -1 ? "80" : String.valueOf(proxyAddress.getPort()));
}
public static @Nullable String parseTripleDotPath(URI importUri) throws URISyntaxException {
var importScheme = importUri.getScheme();
if (importScheme != null) return null;

View File

@@ -1053,3 +1053,7 @@ Certificates can only be loaded from `jar:` or `file:` URLs, but got:\n\
cannotFindBuiltInCertificates=\
Cannot find Pkl's trusted CA certificates on the class path.\n\
To fix this problem, add dependendy `org.pkl:pkl-certs`.
# suppress inspection "HttpUrlsUsage"
malformedProxyAddress=\
Malformed proxy URI (expecting `http://<host>[:<port>]`): `{0}`.

View File

@@ -10,6 +10,7 @@ pkl:base
pkl:Benchmark
pkl:DocPackageInfo
pkl:DocsiteInfo
pkl:EvaluatorSettings
pkl:json
pkl:jsonnet
pkl:math

View File

@@ -0,0 +1,146 @@
package org.pkl.core.http
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.net.URI
@Suppress("HttpUrlsUsage")
class NoProxyRuleTest {
@Test
fun wildcard() {
val noProxyRule = NoProxyRule("*")
assertTrue(noProxyRule.matches(URI("https://foo.com")))
assertTrue(noProxyRule.matches(URI("https://bar.com")))
assertTrue(noProxyRule.matches(URI("https://foo:5000")))
}
@Test
fun `hostname matching`() {
val noProxyRule = NoProxyRule("foo.com")
assertTrue(noProxyRule.matches(URI("https://foo.com")))
assertTrue(noProxyRule.matches(URI("http://foo.com")))
assertTrue(noProxyRule.matches(URI("https://foo.com:5000")))
assertTrue(noProxyRule.matches(URI("https://FOO.COM")))
assertTrue(noProxyRule.matches(URI("https://bar.foo.com")))
assertFalse(noProxyRule.matches(URI("https://bar.foo.com.bar")))
assertFalse(noProxyRule.matches(URI("https://bar.foocom")))
assertFalse(noProxyRule.matches(URI("https://fooo.com")))
assertFalse(noProxyRule.matches(URI("https://ooofoo.com")))
assertFalse(noProxyRule.matches(URI("pkl:foo.com")))
}
@Test
fun `hostname matching, leading dot`() {
val noProxyRule = NoProxyRule(".foo.com")
assertTrue(noProxyRule.matches(URI("https://foo.com")))
assertTrue(noProxyRule.matches(URI("http://foo.com")))
assertTrue(noProxyRule.matches(URI("https://foo.com:5000")))
assertTrue(noProxyRule.matches(URI("https://FOO.COM")))
assertTrue(noProxyRule.matches(URI("https://bar.foo.com")))
assertFalse(noProxyRule.matches(URI("https://bar.foo.com.bar")))
assertFalse(noProxyRule.matches(URI("https://bar.foocom")))
assertFalse(noProxyRule.matches(URI("https://fooo.com")))
assertFalse(noProxyRule.matches(URI("https://ooofoo.com")))
assertFalse(noProxyRule.matches(URI("pkl:foo.com")))
}
@Test
fun `hostname matching, with port`() {
val noProxyRule = NoProxyRule("foo.com:5000")
assertTrue(noProxyRule.matches(URI("https://foo.com:5000")))
assertFalse(noProxyRule.matches(URI("https://foo.com")))
assertFalse(noProxyRule.matches(URI("https://foo.com:3000")))
}
@Test
fun `ipv4 address literal matching`() {
val noProxyRule = NoProxyRule("192.168.1.1")
assertTrue(noProxyRule.matches(URI("http://192.168.1.1:5000")))
assertTrue(noProxyRule.matches(URI("http://192.168.1.1")))
assertTrue(noProxyRule.matches(URI("https://192.168.1.1")))
assertFalse(noProxyRule.matches(URI("https://192.168.1.0")))
assertFalse(noProxyRule.matches(URI("https://192.168.1.0:5000")))
}
@Test
fun `ipv4 address literal matching, with port`() {
val noProxyRule = NoProxyRule("192.168.1.1:5000")
assertTrue(noProxyRule.matches(URI("http://192.168.1.1:5000")))
assertTrue(noProxyRule.matches(URI("https://192.168.1.1:5000")))
assertFalse(noProxyRule.matches(URI("http://192.168.1.1")))
assertFalse(noProxyRule.matches(URI("https://192.168.1.1")))
assertFalse(noProxyRule.matches(URI("http://192.168.1.1:3000")))
assertFalse(noProxyRule.matches(URI("https://192.168.1.1:3000")))
}
@Test
fun `ipv6 address literal matching`() {
val noProxyRule = NoProxyRule("::1")
assertTrue(noProxyRule.matches(URI("http://[::1]")))
assertTrue(noProxyRule.matches(URI("http://[::1]:5000")))
assertTrue(noProxyRule.matches(URI("https://[::1]")))
assertTrue(noProxyRule.matches(URI("https://[0000:0000:0000:0000:0000:0000:0000:0001]")))
assertFalse(noProxyRule.matches(URI("https://[::2]")))
assertFalse(noProxyRule.matches(URI("https://[::2]:5000")))
}
@Test
fun `ipv6 address literal matching, with port`() {
val noProxyRule = NoProxyRule("[::1]:5000")
assertTrue(noProxyRule.matches(URI("http://[::1]:5000")))
assertTrue(noProxyRule.matches(URI("https://[0000:0000:0000:0000:0000:0000:0000:0001]:5000")))
assertFalse(noProxyRule.matches(URI("http://[::1]")))
assertFalse(noProxyRule.matches(URI("https://[::1]")))
assertFalse(noProxyRule.matches(URI("https://[::2]")))
assertFalse(noProxyRule.matches(URI("https://[::2]:5000")))
}
@Test
fun `ipv4 port from protocol`() {
val noProxyRuleHttp = NoProxyRule("192.168.1.1:80")
assertTrue(noProxyRuleHttp.matches(URI("http://192.168.1.1")))
assertTrue(noProxyRuleHttp.matches(URI("http://192.168.1.1:80")))
assertTrue(noProxyRuleHttp.matches(URI("https://192.168.1.1:80")))
assertFalse(noProxyRuleHttp.matches(URI("https://192.168.1.1")))
assertFalse(noProxyRuleHttp.matches(URI("https://192.168.1.1:5000")))
val noProxyRuleHttps = NoProxyRule("192.168.1.1:443")
assertTrue(noProxyRuleHttps.matches(URI("https://192.168.1.1")))
assertTrue(noProxyRuleHttps.matches(URI("http://192.168.1.1:443")))
assertFalse(noProxyRuleHttps.matches(URI("http://192.168.1.1")))
assertFalse(noProxyRuleHttps.matches(URI("https://192.168.1.1:80")))
}
@Test
fun `ipv4 cidr block matching`() {
val noProxyRule1 = NoProxyRule("10.0.0.0/16")
assertTrue(noProxyRule1.matches(URI("https://10.0.0.0")))
assertTrue(noProxyRule1.matches(URI("https://10.0.255.255")))
assertTrue(noProxyRule1.matches(URI("https://10.0.255.255:5000")))
assertFalse(noProxyRule1.matches(URI("https://10.1.0.0")))
assertFalse(noProxyRule1.matches(URI("https://11.0.0.0")))
assertFalse(noProxyRule1.matches(URI("https://9.255.255.255")))
assertFalse(noProxyRule1.matches(URI("https://9.255.255.255:5000")))
val noProxyRule2 = NoProxyRule("10.0.0.0/32")
assertTrue(noProxyRule2.matches(URI("https://10.0.0.0")))
assertTrue(noProxyRule2.matches(URI("https://10.0.0.0:5000")))
assertFalse(noProxyRule2.matches(URI("https://10.0.0.1")))
assertFalse(noProxyRule2.matches(URI("https://9.255.255.55:5000")))
}
@Test
fun `ipv6 cidr block matching`() {
val noProxyRule1 = NoProxyRule("1000::ff/32")
assertTrue(noProxyRule1.matches(URI("https://[1000::]")))
assertTrue(noProxyRule1.matches(URI("https://[1000:0:ffff:ffff:ffff:ffff:ffff:ffff]")))
assertFalse(noProxyRule1.matches(URI("https://[999::]")))
assertFalse(noProxyRule1.matches(URI("https://[1000:1::]")))
val noProxyRule2 = NoProxyRule("1000::ff/128")
assertTrue(noProxyRule2.matches(URI("https://[1000::ff]")))
assertFalse(noProxyRule2.matches(URI("https://[999::]")))
assertFalse(noProxyRule2.matches(URI("https://[1001::]")))
}
}

View File

@@ -10,7 +10,7 @@ import org.pkl.commons.writeString
import org.pkl.core.*
import org.pkl.core.http.HttpClient
import org.pkl.core.packages.PackageUri
import org.pkl.core.project.Project.EvaluatorSettings
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings
import java.net.URI
import java.nio.file.Path
import java.util.regex.Pattern
@@ -40,7 +40,7 @@ class ProjectTest {
listOf(Path.of("apiTest1.pkl"), Path.of("apiTest2.pkl")),
listOf("PklProject", "PklProject.deps.json", ".**", "*.exe")
)
val expectedSettings = EvaluatorSettings(
val expectedSettings = PklEvaluatorSettings(
mapOf("two" to "2"),
mapOf("one" to "1"),
listOf("foo:", "bar:").map(Pattern::compile),
@@ -52,7 +52,8 @@ class ProjectTest {
path.resolve("modulepath2/")
),
Duration.ofMinutes(5.0),
path
path,
null
)
projectPath.writeString("""
amends "pkl:Project"
@@ -116,7 +117,7 @@ class ProjectTest {
""".trimIndent())
val project = Project.loadFromPath(projectPath)
assertThat(project.`package`).isEqualTo(expectedPackage)
assertThat(project.settings).isEqualTo(expectedSettings)
assertThat(project.evaluatorSettings).isEqualTo(expectedSettings)
assertThat(project.tests).isEqualTo(listOf(path.resolve("test1.pkl"), path.resolve("test2.pkl")))
}

View File

@@ -4,13 +4,18 @@ import java.nio.file.Path
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatCode
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.io.TempDir
import org.pkl.commons.createParentDirectories
import org.pkl.commons.writeString
import org.pkl.core.Evaluator
import org.pkl.core.ModuleSource
import org.pkl.core.PObject
import org.pkl.core.StackFrameTransformers
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings
import org.pkl.core.runtime.VmException
import org.pkl.core.settings.PklSettings.Editor
import java.net.URI
class PklSettingsTest {
@Test
@@ -25,7 +30,61 @@ class PklSettingsTest {
)
val settings = PklSettings.loadFromPklHomeDir(tempDir)
assertThat(settings).isEqualTo(PklSettings(Editor.SUBLIME))
assertThat(settings).isEqualTo(PklSettings(Editor.SUBLIME, null))
}
@Test
fun `load user settings with http`(@TempDir tempDir: Path) {
val settingsPath = tempDir.resolve("settings.pkl")
settingsPath.createParentDirectories()
settingsPath.writeString(
"""
amends "pkl:settings"
http {
proxy {
address = "http://localhost:8080"
noProxy {
"example.com"
"pkg.pkl-lang.org"
}
}
}
""".trimIndent()
)
val settings = PklSettings.loadFromPklHomeDir(tempDir)
val expectedHttp = PklEvaluatorSettings.Http(
PklEvaluatorSettings.Proxy(
URI("http://localhost:8080"),
listOf("example.com", "pkg.pkl-lang.org")
)
)
assertThat(settings).isEqualTo(PklSettings(Editor.SYSTEM, expectedHttp))
}
@Test
fun `load user settings with http, but no noProxy`(@TempDir tempDir: Path) {
val settingsPath = tempDir.resolve("settings.pkl")
settingsPath.createParentDirectories()
settingsPath.writeString(
"""
amends "pkl:settings"
http {
proxy {
address = "http://localhost:8080"
}
}
""".trimIndent()
)
val settings = PklSettings.loadFromPklHomeDir(tempDir)
val expectedHttp = PklEvaluatorSettings.Http(
PklEvaluatorSettings.Proxy(
URI("http://localhost:8080"),
listOf(),
)
)
assertThat(settings).isEqualTo(PklSettings(Editor.SYSTEM, expectedHttp))
}
@Test
@@ -39,7 +98,7 @@ class PklSettingsTest {
)
val settings = PklSettings.load(ModuleSource.path(settingsPath))
assertThat(settings).isEqualTo(PklSettings(Editor.IDEA))
assertThat(settings).isEqualTo(PklSettings(Editor.IDEA, null))
}
@Test
@@ -76,6 +135,6 @@ class PklSettingsTest {
}
private fun checkEquals(expected: Editor, actual: PObject) {
assertThat(actual.getProperty("urlScheme") as String).isEqualTo(expected.urlScheme)
assertThat(actual.getProperty("urlScheme") as String).isEqualTo(expected.urlScheme())
}
}

View File

@@ -11,14 +11,14 @@ com.google.guava:guava:32.1.1-jre=testCompileClasspath,testImplementationDepende
com.google.j2objc:j2objc-annotations:2.8=testCompileClasspath,testImplementationDependenciesMetadata
com.google.jimfs:jimfs:1.3.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.ibm.icu:icu4j:58.2=validator
com.ibm.icu:icu4j:71.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.ibm.icu:icu4j:72.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.shapesecurity:salvation:2.7.2=validator
com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath
commons-codec:commons-codec:1.10=validator
commons-io:commons-io:2.4=validator
commons-logging:commons-logging:1.2=validator
isorelax:isorelax:20030108=validator
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
net.sf.saxon:Saxon-HE:9.6.0-4=validator
nu.validator:cssvalidator:1.0.8=validator
@@ -32,7 +32,7 @@ org.apache.httpcomponents:httpcore:4.4=validator
org.apache.logging.log4j:log4j-1.2-api:2.17.1=validator
org.apache.logging.log4j:log4j-api:2.17.1=validator
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.checkerframework:checker-qual:3.33.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.commonmark:commonmark-ext-gfm-tables:0.21.0=default
org.commonmark:commonmark-ext-gfm-tables:0.22.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath

View File

@@ -2,10 +2,10 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.tunnelvisionlabs:antlr4-runtime:4.9.0=testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.sdk:graal-sdk:23.0.2=testRuntimeClasspath
org.graalvm.truffle:truffle-api:23.0.2=testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -3,10 +3,10 @@
# This file is expected to be part of source control.
com.github.ajalt.clikt:clikt-jvm:3.5.1=compileClasspath
com.github.ajalt.clikt:clikt:3.5.1=compileClasspath,compileOnlyDependenciesMetadata
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -281,6 +281,8 @@ public class PklPlugin implements Plugin<Project> {
spec.getNoCache().convention(false);
spec.getTestPort().convention(-1);
spec.getNoProxy().convention(List.of());
}
private void configureCodeGenSpec(CodeGenSpec spec) {
@@ -424,6 +426,8 @@ public class PklPlugin implements Plugin<Project> {
task.getModuleCacheDir().set(spec.getModuleCacheDir());
task.getEvalTimeout().set(spec.getEvalTimeout());
task.getTestPort().set(spec.getTestPort());
task.getProxyAddress().set(spec.getProxyAddress());
task.getNoProxy().set(spec.getNoProxy());
}
private <T extends ModulesTask, S extends ModulesSpec> void configureModulesTask(T task, S spec) {

View File

@@ -15,6 +15,7 @@
*/
package org.pkl.gradle.spec;
import java.net.URI;
import java.time.Duration;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
@@ -50,4 +51,8 @@ public interface BasePklSpec {
Property<Duration> getEvalTimeout();
Property<Integer> getTestPort();
Property<URI> getProxyAddress();
ListProperty<String> getNoProxy();
}

View File

@@ -125,6 +125,14 @@ public abstract class BasePklTask extends DefaultTask {
@Optional
public abstract Property<Integer> getTestPort();
@Input
@Optional
public abstract Property<URI> getProxyAddress();
@Input
@Optional
public abstract ListProperty<String> getNoProxy();
@TaskAction
public void runTask() {
doRunTask();
@@ -156,7 +164,9 @@ public abstract class BasePklTask extends DefaultTask {
false,
false,
getTestPort().getOrElse(-1),
Collections.emptyList());
Collections.emptyList(),
getProxyAddress().getOrNull(),
getNoProxy().getOrElse(List.of()));
}
return cachedOptions;
}

View File

@@ -178,7 +178,9 @@ public abstract class ModulesTask extends BasePklTask {
getNoProject().getOrElse(false),
false,
getTestPort().getOrElse(-1),
Collections.emptyList());
Collections.emptyList(),
null,
List.of());
}
return cachedOptions;
}

View File

@@ -2,10 +2,10 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.tunnelvisionlabs:antlr4-runtime:4.9.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.11=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.16=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.25.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.assertj:assertj-core:3.26.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.sdk:graal-sdk:23.0.2=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.truffle:truffle-api:23.0.2=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath

View File

@@ -20,6 +20,7 @@ import java.nio.file.Path
import java.time.Duration
import java.util.*
import java.util.regex.Pattern
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings.*
import org.pkl.core.module.PathElement
import org.pkl.core.packages.Checksums
@@ -123,6 +124,7 @@ data class CreateEvaluatorRequest(
val cacheDir: Path?,
val outputFormat: String?,
val project: Project?,
val http: Http?,
) : ClientRequestMessage() {
override val type = MessageType.CREATE_EVALUATOR_REQUEST

View File

@@ -23,6 +23,7 @@ import org.msgpack.core.MessageTypeException
import org.msgpack.core.MessageUnpacker
import org.msgpack.value.Value
import org.msgpack.value.impl.ImmutableStringValueImpl
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings
import org.pkl.core.module.PathElement
import org.pkl.core.packages.Checksums
@@ -50,8 +51,8 @@ internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : Messa
allowedModules = map.unpackStringListOrNull("allowedModules")?.map(Pattern::compile),
allowedResources =
map.unpackStringListOrNull("allowedResources")?.map(Pattern::compile),
clientModuleReaders = map.unpackModuleReaderSpec("clientModuleReaders"),
clientResourceReaders = map.unpackResourceReaderSpec("clientResourceReaders"),
clientModuleReaders = map.unpackModuleReaderSpec(),
clientResourceReaders = map.unpackResourceReaderSpec(),
modulePaths = map.unpackStringListOrNull("modulePaths")?.map(Path::of),
env = map.unpackStringMapOrNull("env"),
properties = map.unpackStringMapOrNull("properties"),
@@ -59,7 +60,8 @@ internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : Messa
rootDir = map.unpackStringOrNull("rootDir")?.let(Path::of),
cacheDir = map.unpackStringOrNull("cacheDir")?.let(Path::of),
outputFormat = map.unpackStringOrNull("outputFormat"),
project = map.unpackProject("project")
project = map.unpackProject(),
http = map.unpackHttp(),
)
}
MessageType.CREATE_EVALUATOR_RESPONSE.code -> {
@@ -221,8 +223,8 @@ internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : Messa
PathElement(map.unpackString("name"), map.unpackBoolean("isDirectory"))
}
private fun Map<Value, Value>.unpackModuleReaderSpec(name: String): List<ModuleReaderSpec>? {
val keys = getNullable(name) ?: return null
private fun Map<Value, Value>.unpackModuleReaderSpec(): List<ModuleReaderSpec>? {
val keys = getNullable("clientModuleReaders") ?: return null
return keys.asArrayValue().toList().map { value ->
val readerMap = value.asMapValue().map()
ModuleReaderSpec(
@@ -234,8 +236,8 @@ internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : Messa
}
}
private fun Map<Value, Value>.unpackResourceReaderSpec(name: String): List<ResourceReaderSpec> {
val keys = getNullable(name) ?: return emptyList()
private fun Map<Value, Value>.unpackResourceReaderSpec(): List<ResourceReaderSpec> {
val keys = getNullable("clientResourceReaders") ?: return emptyList()
return keys.asArrayValue().toList().map { value ->
val readerMap = value.asMapValue().map()
ResourceReaderSpec(
@@ -246,13 +248,26 @@ internal class MessagePackDecoder(private val unpacker: MessageUnpacker) : Messa
}
}
private fun Map<Value, Value>.unpackProject(name: String): Project? {
val projMap = getNullable(name)?.asMapValue()?.map() ?: return null
private fun Map<Value, Value>.unpackProject(): Project? {
val projMap = getNullable("project")?.asMapValue()?.map() ?: return null
val projectFileUri = URI(projMap.unpackString("projectFileUri"))
val dependencies = projMap.unpackDependencies("dependencies")
return Project(projectFileUri, null, dependencies)
}
private fun Map<Value, Value>.unpackHttp(): PklEvaluatorSettings.Http? {
val httpMap = getNullable("http")?.asMapValue()?.map() ?: return null
val proxy = httpMap.unpackProxy()
return PklEvaluatorSettings.Http(proxy)
}
private fun Map<Value, Value>.unpackProxy(): PklEvaluatorSettings.Proxy? {
val proxyMap = getNullable("proxy")?.asMapValue()?.map() ?: return null
val address = proxyMap.unpackString("address")
val noProxy = proxyMap.unpackStringListOrNull("noProxy")
return PklEvaluatorSettings.Proxy.create(address, noProxy)
}
private fun Map<Value, Value>.unpackDependencies(name: String): Map<String, Dependency> {
val mapValue = get(name).asMapValue().map()
return mapValue.entries.associate { (key, value) ->

View File

@@ -29,9 +29,9 @@ import org.pkl.core.packages.PackageUri
import org.pkl.core.project.DeclaredDependencies
import org.pkl.core.resource.ResourceReader
import org.pkl.core.resource.ResourceReaders
import org.pkl.core.util.IoUtils
class Server(private val transport: MessageTransport, private val httpClient: HttpClient) :
AutoCloseable {
class Server(private val transport: MessageTransport) : AutoCloseable {
private val evaluators: MutableMap<Long, BinaryEvaluator> = ConcurrentHashMap()
// https://github.com/jano7/executor would be the perfect executor here
@@ -162,6 +162,14 @@ class Server(private val transport: MessageTransport, private val httpClient: Ht
val properties = message.properties ?: emptyMap()
val timeout = message.timeout
val cacheDir = message.cacheDir
val http =
with(HttpClient.builder()) {
message.http?.proxy?.let { proxy ->
setProxy(proxy.address, message.http.proxy?.noProxy ?: listOf())
proxy.address?.let(IoUtils::setSystemProxy)
}
buildLazily()
}
val dependencies =
message.project?.let { proj ->
buildDeclaredDependencies(proj.projectFileUri, proj.dependencies, null)
@@ -175,7 +183,7 @@ class Server(private val transport: MessageTransport, private val httpClient: Ht
SecurityManagers.defaultTrustLevels,
rootDir
),
httpClient,
http,
ClientLogger(evaluatorId, transport),
createModuleKeyFactories(message, evaluatorId, resolver),
createResourceReaders(message, evaluatorId, resolver),

View File

@@ -1029,7 +1029,8 @@ abstract class AbstractServerTest {
rootDir = null,
cacheDir = cacheDir,
outputFormat = null,
project = project
project = project,
http = null,
)
send(message)

View File

@@ -19,7 +19,6 @@ import java.io.PipedInputStream
import java.io.PipedOutputStream
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.pkl.core.http.HttpClient
class JvmServerTest : AbstractServerTest() {
private val transports: Pair<MessageTransport, MessageTransport> = run {
@@ -35,7 +34,7 @@ class JvmServerTest : AbstractServerTest() {
}
override val client: TestTransport = TestTransport(transports.first)
private val server: Server = Server(transports.second, HttpClient.dummyClient())
private val server: Server = Server(transports.second)
@BeforeEach
fun beforeEach() {

View File

@@ -112,7 +112,8 @@ class MessagePackCodecTest {
"baz" to
RemoteDependency(URI("package://localhost:0/baz@1.1.0"), Checksums("abc123"))
)
)
),
http = null,
)
)
}

View File

@@ -28,14 +28,10 @@ org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1=default,runtimeClasspath,
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.5.1=default,runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1=default,runtimeClasspath,testRuntimeClasspath
org.jetbrains:annotations:13.0=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jline:jline-native:3.23.0=default
org.jline:jline-native:3.23.0=runtimeClasspath,testRuntimeClasspath
org.jline:jline-reader:3.23.0=default
org.jline:jline-reader:3.23.0=runtimeClasspath,testRuntimeClasspath
org.jline:jline-terminal-jansi:3.23.0=default
org.jline:jline-terminal-jansi:3.23.0=runtimeClasspath,testRuntimeClasspath
org.jline:jline-terminal:3.23.0=default
org.jline:jline-terminal:3.23.0=runtimeClasspath,testRuntimeClasspath
org.jline:jline-native:3.23.0=default,runtimeClasspath,testRuntimeClasspath
org.jline:jline-reader:3.23.0=default,runtimeClasspath,testRuntimeClasspath
org.jline:jline-terminal-jansi:3.23.0=default,runtimeClasspath,testRuntimeClasspath
org.jline:jline-terminal:3.23.0=default,runtimeClasspath,testRuntimeClasspath
org.msgpack:msgpack-core:0.9.0=default,runtimeClasspath,testRuntimeClasspath
org.organicdesign:Paguro:3.10.3=default,runtimeClasspath,testRuntimeClasspath
org.snakeyaml:snakeyaml-engine:2.5=default,runtimeClasspath,testRuntimeClasspath

View File

@@ -0,0 +1,140 @@
//===----------------------------------------------------------------------===//
// Copyright © 2024 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.
// You may obtain a copy of the License at
//
// https://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.
//===----------------------------------------------------------------------===//
/// Common settings for Pkl's own evaluator.
@ModuleInfo { minPklVersion = "0.26.0" }
@Since { version = "0.26.0" }
module pkl.EvaluatorSettings
/// The external properties available to Pkl, read using the `prop:` scheme.
externalProperties: Mapping<String, String>?
/// The environment variables available to Pkl, read using the `env:` scheme.
///
/// Example:
/// ```
/// env {
/// ["IS_PROD"] = "true"
/// }
/// ```
env: Mapping<String, String>?
/// The set of module URI patterns that can be imported.
///
/// Each element is a regular expression pattern that is tested against a module import.
///
/// Modules are imported either through an amends or extends clause, an import clause, or an
/// import expression.
///
/// Example:
/// ```
/// allowedModules {
/// "https:"
/// "file:"
/// "package:"
/// "projectpackage:"
/// }
/// ```
allowedModules: Listing<String(isRegex)>?
/// The set of resource URI patterns that can be imported.
///
/// Each element is a regular expression pattern that is tested against a resource read.
///
/// Example:
/// ```
/// allowedResources {
/// "https:"
/// "file:"
/// "package:"
/// "projectpackage:"
/// "env:"
/// "prop:"
/// }
/// ```
allowedResources: Listing<String(isRegex)>?
/// Disables the file system cache for `package:` modules.
///
/// When caching is disabled, packages are loaded over the network and stored in memory.
noCache: Boolean?
/// A collection of jars, zips, or directories to be placed into the module path.
///
/// Module path modules and resources may be read and imported using the `modulepath:` scheme.
modulePath: Listing<String>?
/// The duration after which evaluation of a source module will be timed out.
///
/// Note that a timeout is treated the same as a program error in that any subsequent source modules will not be evaluated.
timeout: Duration?
/// The directory where `package:` modules are cached.
moduleCacheDir: String?
/// Restricts access to file-based modules and resources to those located under this directory.
rootDir: String?
/// Configuration of outgoing HTTP requests.
http: Http?
/// Settings that control how Pkl talks to HTTP(S) servers.
class Http {
/// Configuration of the HTTP proxy to use.
///
/// If [null], uses the operating system's proxy configuration.
proxy: Proxy?
}
/// Settings that control how Pkl talks to HTTP proxies.
class Proxy {
/// The proxy to use for HTTP(S) connections.
///
/// At the moment, only HTTP proxies are supported.
///
/// Example:
/// ```
/// address = "http://my.proxy.example.com:5080"
/// ```
address: Uri(startsWith("http://"))?
/// Hosts to which all connections should bypass a proxy.
///
/// Values can be either hostnames, or IP addresses.
/// IP addresses can optionally be provided using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation).
///
/// The only wildcard is `"*"`, which disables all proxying.
///
/// A hostname matches all subdomains.
/// For example, `example.com` matches `foo.example.com`, but not `fooexample.com`.
/// A hostname that is prefixed with a dot matches the hostname itself,
/// so `.example.com` matches `example.com`.
///
/// Optionally, a port can be specified.
/// If a port is omitted, all ports are matched.
///
/// Example:
///
/// ```
/// noProxy {
/// "127.0.0.1"
/// "169.254.0.0/16"
/// "example.com"
/// "localhost:5050"
/// }
/// ```
noProxy: Listing<String>(isDistinct)
}

View File

@@ -67,6 +67,7 @@
@ModuleInfo { minPklVersion = "0.26.0" }
module pkl.Project
import "pkl:EvaluatorSettings" as EvaluatorSettingsModule
import "pkl:Project"
import "pkl:reflect"
import "pkl:semver"
@@ -195,7 +196,9 @@ local isFileBasedProject = projectFileUri.startsWith("file:")
/// - [modulePath][EvaluatorSettings.modulePath]
/// - [rootDir][EvaluatorSettings.rootDir]
/// - [moduleCacheDir][EvaluatorSettings.moduleCacheDir]
evaluatorSettings: EvaluatorSettings(
///
/// For each of these, relative paths are resolved against the project's enclosing directory.
evaluatorSettings: EvaluatorSettingsModule(
(modulePath != null).implies(isFileBasedProject),
(rootDir != null).implies(isFileBasedProject),
(moduleCacheDir != null).implies(isFileBasedProject)
@@ -403,82 +406,8 @@ class Package {
fixed uri: PackageUri = "\(baseUri)@\(version)"
}
class EvaluatorSettings {
/// The external properties available to Pkl, read using the `prop:` scheme.
externalProperties: Mapping<String, String>?
/// The environment variables available to Pkl, read using the `env:` scheme.
///
/// Example:
/// ```
/// env {
/// ["IS_PROD"] = "true"
/// }
/// ```
env: Mapping<String, String>?
/// The set of module URI patterns that can be imported.
///
/// Each element is a regular expression pattern that is tested against a module import.
///
/// Modules are imported either through an amends or extends clause, an import clause, or an
/// import expression.
///
/// Example:
/// ```
/// allowedModules {
/// "https:"
/// "file:"
/// "package:"
/// "projectpackage:"
/// }
/// ```
allowedModules: Listing<String(isRegex)>?
/// The set of resource URI patterns that can be imported.
///
/// Each element is a regular expression pattern that is tested against a resource read.
///
/// Example:
/// ```
/// allowedResources {
/// "https:"
/// "file:"
/// "package:"
/// "projectpackage:"
/// "env:"
/// "prop:"
/// }
/// ```
allowedResources: Listing<String(isRegex)>?
/// Disables the file system cache for `package:` modules.
///
/// When caching is disabled, packages are loaded over the network and stored in memory.
noCache: Boolean?
/// A collection of jars, zips, or directories to be placed into the module path.
///
/// Module path modules and resources may be read and imported using the `modulepath:` scheme.
///
/// Relative paths are resolved against PklProject's enclosing directory.
modulePath: Listing<String>?
/// The duration after which evaluation of a source module will be timed out.
///
/// Note that a timeout is treated the same as a program error in that any subsequent source modules will not be evaluated.
timeout: Duration?
/// The directory where `package:` modules are cached.
///
/// Relative paths are resolved against PklProject's enclosing directory.
moduleCacheDir: String?
/// Restricts access to file-based modules and resources to those located under this directory.
///
/// Relative paths are resolved against PklProject's enclosing directory.
rootDir: String?
}
@Deprecated { since = "0.26.0"; replaceWith = "EvaluatorSettingsModule" }
typealias EvaluatorSettings = EvaluatorSettingsModule
/// Common software licenses in the [SPDX License List](https://spdx.org/licenses/).
typealias CommonSpdxLicenseIdentifier =

View File

@@ -22,9 +22,15 @@
@ModuleInfo { minPklVersion = "0.26.0" }
module pkl.settings
import "pkl:EvaluatorSettings"
/// The editor for viewing and editing Pkl files.
editor: Editor = System
/// Settings for controlling how Pkl makes HTTP(S) requests.
@Since { version = "0.26.0" }
http: EvaluatorSettings.Http?
/// The editor associated with `file:` URLs ending in `.pkl`.
hidden System: Editor = new {
urlScheme = "%{url}, line %{line}"