Add netrc helpers in EvaluatorSettings.pkl (#1817)

This adds the `netRcHeaders` helper method that parses netrc file contents, and returns a mapping suitable to be used as Http headers.
This commit is contained in:
Yarden Bar
2026-09-09 14:25:06 -07:00
committed by GitHub
parent 713adac824
commit c95b3d11f4
9 changed files with 1007 additions and 0 deletions
@@ -0,0 +1,159 @@
amends "../snippetTest.pkl"
import "pkl:EvaluatorSettings"
local const http = new EvaluatorSettings.Http {}
examples {
["netRcHeaders - basic"] {
http.netRcHeaders("""
machine github.com
login octocat
password secret_pass
""")
}
["netRcHeaders - quotes"] {
http.netRcHeaders("""
machine foo.com
login "bar bar"
password baz
""")
}
["netRcHeaders - trailing comments"] {
http.netRcHeaders("""
machine foo.com
login bar
password foo # some comment
machine bar.com
login bar2
password foo#some comment
""")
}
["netRcHeaders - hash inside quote"] {
http.netRcHeaders("""
machine foo.com
login "foo # bar"
password "pass#1"
""")
}
["netRcHeaders - tabs as whitespace"] {
http.netRcHeaders("machine\tfoo.com\tlogin\tbar\tpassword\tbaz")
}
["netRcHeaders - crlf line endings"] {
http.netRcHeaders("machine crlf.example.com\r\n login foo\r\n password bar\r\n")
}
["netRcHeaders - entry with login but no password"] {
http.netRcHeaders("""
machine nologin.com
password onlypass
machine nopass.com
login onlylogin
machine valid.com
login user
password pass
""")
}
["netRcHeaders - duplicate machines"] {
http.netRcHeaders("""
machine foo.com login firstUser password firstPass
machine foo.com login secondUser password secondPass
""")
}
["netRcHeaders - machine that collides with keyword"] {
http.netRcHeaders("""
machine machine login user password pass
""")
}
["netRcHeaders - machine default and default keyword"] {
http.netRcHeaders("""
default login defUser password defPass
machine default login machDefUser password machDefPass
""")
}
["netRcHeaders - machine with slash omitted"] {
http.netRcHeaders("""
machine foo.com/bar login user password pass
machine valid.com login user password pass
""")
}
["netRcHeaders - machine with glob characters escaped"] {
http.netRcHeaders("""
machine foo[bar].com login user password pass
machine star*.com login user password pass
""")
}
["netRcHeaders - files with only comments or whitespace"] {
http.netRcHeaders("")
http.netRcHeaders("# only comments\n# second comment")
http.netRcHeaders(" \t \n \n ")
}
["netRcHeaders - skipped macdef"] {
http.netRcHeaders("""
macdef init
echo hello
echo world
machine foo.com login user1 password pass1
""")
}
["netRcHeaders - macdef as value"] {
http.netRcHeaders("""
machine foo.com login macdef password pass1
""")
}
["netRcHeaders - empty quotes"] {
http.netRcHeaders("""
machine foo.com login "" password ""
""")
}
["netRcHeaders - escaped quotes and chars"] {
http.netRcHeaders(#"""
machine foo.com login "foo\"bar" password "foo \z"
"""#)
}
["netRcHeaders - consecutive quotes"] {
http.netRcHeaders("""
machine foo.com login "foo""bar" password pass
""")
}
["netRcHeaders - quoted keywords"] {
http.netRcHeaders("""
"machine" foo.com "login" user "password" pass
""")
}
["netRcHeaders - single line entry"] {
http.netRcHeaders("machine foo.com login bar password pass1")
}
["netRcHeaders - trailing machine at EOF"] {
http.netRcHeaders("machine foo.com login bar password pass1 machine")
}
["netRcHeaders - unterminated quote error"] {
module.catch(() -> http.netRcHeaders("""
machine foo.com login "foo
"""))
module.catch(() -> http.netRcHeaders("""
machine foo.com login "foo\\
"""))
}
}
@@ -0,0 +1,211 @@
examples {
["netRcHeaders - basic"] {
new {
["http{,s}://github.com/**"] {
["Authorization"] {
"Basic b2N0b2NhdDpzZWNyZXRfcGFzcw=="
}
}
}
}
["netRcHeaders - quotes"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic YmFyIGJhcjpiYXo="
}
}
}
}
["netRcHeaders - trailing comments"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic YmFyOmZvbw=="
}
}
["http{,s}://bar.com/**"] {
["Authorization"] {
"Basic YmFyMjpmb28jc29tZQ=="
}
}
}
}
["netRcHeaders - hash inside quote"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic Zm9vICMgYmFyOnBhc3MjMQ=="
}
}
}
}
["netRcHeaders - tabs as whitespace"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic YmFyOmJheg=="
}
}
}
}
["netRcHeaders - crlf line endings"] {
new {
["http{,s}://crlf.example.com/**"] {
["Authorization"] {
"Basic Zm9vOmJhcg=="
}
}
}
}
["netRcHeaders - entry with login but no password"] {
new {
["http{,s}://nologin.com/**"] {
["Authorization"] {
"Basic Om9ubHlwYXNz"
}
}
["http{,s}://nopass.com/**"] {
["Authorization"] {
"Basic b25seWxvZ2luOg=="
}
}
["http{,s}://valid.com/**"] {
["Authorization"] {
"Basic dXNlcjpwYXNz"
}
}
}
}
["netRcHeaders - duplicate machines"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic Zmlyc3RVc2VyOmZpcnN0UGFzcw=="
}
}
}
}
["netRcHeaders - machine that collides with keyword"] {
new {
["http{,s}://machine/**"] {
["Authorization"] {
"Basic dXNlcjpwYXNz"
}
}
}
}
["netRcHeaders - machine default and default keyword"] {
new {
["http{,s}://default/**"] {
["Authorization"] {
"Basic bWFjaERlZlVzZXI6bWFjaERlZlBhc3M="
}
}
}
}
["netRcHeaders - machine with slash omitted"] {
new {
["http{,s}://valid.com/**"] {
["Authorization"] {
"Basic dXNlcjpwYXNz"
}
}
}
}
["netRcHeaders - machine with glob characters escaped"] {
new {
["http{,s}://foo[[]bar].com/**"] {
["Authorization"] {
"Basic dXNlcjpwYXNz"
}
}
["http{,s}://star[*].com/**"] {
["Authorization"] {
"Basic dXNlcjpwYXNz"
}
}
}
}
["netRcHeaders - files with only comments or whitespace"] {
new {}
new {}
new {}
}
["netRcHeaders - skipped macdef"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic dXNlcjE6cGFzczE="
}
}
}
}
["netRcHeaders - macdef as value"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic bWFjZGVmOnBhc3Mx"
}
}
}
}
["netRcHeaders - empty quotes"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic Og=="
}
}
}
}
["netRcHeaders - escaped quotes and chars"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic Zm9vImJhcjpmb28geg=="
}
}
}
}
["netRcHeaders - consecutive quotes"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic Zm9vOnBhc3M="
}
}
}
}
["netRcHeaders - quoted keywords"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic dXNlcjpwYXNz"
}
}
}
}
["netRcHeaders - single line entry"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic YmFyOnBhc3Mx"
}
}
}
}
["netRcHeaders - trailing machine at EOF"] {
new {
["http{,s}://foo.com/**"] {
["Authorization"] {
"Basic YmFyOnBhc3Mx"
}
}
}
}
["netRcHeaders - unterminated quote error"] {
"Cannot parse .netrc: unclosed quote."
"Cannot parse .netrc: invalid escape."
}
}
@@ -17,6 +17,7 @@ package org.pkl.core.settings
import java.net.URI
import java.nio.file.Path
import java.util.Base64
import kotlin.io.path.createParentDirectories
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatCode
@@ -175,6 +176,60 @@ class PklSettingsTest {
)
}
@Test
fun `load user settings with netRcHeaders`(@TempDir tempDir: Path) {
val netrcFile = tempDir.resolve(".netrc")
netrcFile.writeString(
"""
machine github.com
login octocat
password secret_pass
"""
.trimIndent()
)
val settingsPath = tempDir.resolve("settings.pkl")
settingsPath.writeString(
"""
amends "pkl:settings"
http {
headers = netRcHeaders(read("${netrcFile.toUri()}"))
}
"""
.trimIndent()
)
val settings = PklSettings.load(ModuleSource.path(settingsPath))
val expectedAuth =
"Basic " + Base64.getEncoder().encodeToString("octocat:secret_pass".toByteArray())
val expectedHttp =
PklEvaluatorSettings.Http(
null,
null,
mapOf("http{,s}://github.com/**" to mapOf("Authorization" to listOf(expectedAuth))),
)
assertThat(settings.http()).isEqualTo(expectedHttp)
}
@Test
fun `test import EvaluatorSettings`() {
val evaluator = Evaluator.preconfigured()
val module =
evaluator.evaluate(
ModuleSource.text(
"""
import "pkl:EvaluatorSettings"
res = (new EvaluatorSettings.Http {}).netRcHeaders("")
"""
.trimIndent()
)
)
assertThat(module.getProperty("res")).isNotNull
}
private fun checkEquals(expected: Editor, actual: PObject) {
assertThat(actual.getProperty("urlScheme") as String).isEqualTo(expected.urlScheme())
}
@@ -0,0 +1,207 @@
/*
* Copyright © 2026 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.util
import java.util.Base64
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.Test
class NetrcTest {
@Test
fun `parse netrc file content`() {
val content =
"""
# Comment line
machine github.com
login octocat
password secret_token_123
machine my-artifactory.internal.net
login user
password my_token
default
login defaultuser
password defaultpass
"""
.trimIndent()
val entries = Netrc.parse(content)
assertThat(entries)
.containsExactly(
Netrc.Entry("github.com", false, "octocat", "secret_token_123", null),
Netrc.Entry("my-artifactory.internal.net", false, "user", "my_token", null),
Netrc.Entry("default", true, "defaultuser", "defaultpass", null),
)
val headersMap = Netrc.toHeadersMap(entries)
val basicGithub =
"Basic " + Base64.getEncoder().encodeToString("octocat:secret_token_123".toByteArray())
val basicArtifactory =
"Basic " + Base64.getEncoder().encodeToString("user:my_token".toByteArray())
assertThat(headersMap["http{,s}://github.com/**"])
.isEqualTo(mapOf("Authorization" to listOf(basicGithub)))
assertThat(headersMap["http{,s}://my-artifactory.internal.net/**"])
.isEqualTo(mapOf("Authorization" to listOf(basicArtifactory)))
assertThat(headersMap["**"]).isNull()
}
@Test
fun `parse quotes and comments`() {
val content =
"""
# Header comment
machine example.com login "user with spaces" password "pass#with#hash" # trailing comment
machine "quoted.machine.com" login "quoteduser" password "token_with_quotes"
"""
.trimIndent()
val entries = Netrc.parse(content)
assertThat(entries)
.containsExactly(
Netrc.Entry("example.com", false, "user with spaces", "pass#with#hash", null),
Netrc.Entry("quoted.machine.com", false, "quoteduser", "token_with_quotes", null),
)
val headersMap = Netrc.toHeadersMap(entries)
val expectedBasic =
"Basic " + Base64.getEncoder().encodeToString("user with spaces:pass#with#hash".toByteArray())
val expectedQuoted =
"Basic " + Base64.getEncoder().encodeToString("quoteduser:token_with_quotes".toByteArray())
assertThat(headersMap["http{,s}://example.com/**"])
.isEqualTo(mapOf("Authorization" to listOf(expectedBasic)))
assertThat(headersMap["http{,s}://quoted.machine.com/**"])
.isEqualTo(mapOf("Authorization" to listOf(expectedQuoted)))
}
@Test
fun `escape sequences in quoted strings`() {
val content =
"""
machine example.com login "user\nwith\tcontrol\rchars" password "pass\"with\\escapes"
"""
.trimIndent()
val entries = Netrc.parse(content)
assertThat(entries)
.containsExactly(
Netrc.Entry("example.com", false, "user\nwith\tcontrol\rchars", "pass\"with\\escapes", null)
)
}
@Test
fun `case-insensitive keywords`() {
val content =
"""
MACHINE example.com LOGIN myUser PASSWORD myPass ACCOUNT myAccount
DEFAULT LOGIN defUser PASSWORD defPass
"""
.trimIndent()
val entries = Netrc.parse(content)
assertThat(entries)
.containsExactly(
Netrc.Entry("example.com", false, "myUser", "myPass", "myAccount"),
Netrc.Entry("default", true, "defUser", "defPass", null),
)
}
@Test
fun `discard unexpected tokens`() {
val content =
"""
machine example.com login foo bar baz password qux extra1 extra2
"""
.trimIndent()
val entries = Netrc.parse(content)
assertThat(entries).containsExactly(Netrc.Entry("example.com", false, "foo", "qux", null))
}
@Test
fun `skip macdef sections`() {
val content =
"""
macdef init
echo hello
echo world
machine foo.com login user1 password pass1
"""
.trimIndent()
val entries = Netrc.parse(content)
assertThat(entries).containsExactly(Netrc.Entry("foo.com", false, "user1", "pass1", null))
}
@Test
fun `skip macdef sections with crlf`() {
val content =
"macdef init\r\n echo hello\r\n echo world\r\n\r\nmachine foo.com login user1 password pass1\r\n"
val entries = Netrc.parse(content)
assertThat(entries).containsExactly(Netrc.Entry("foo.com", false, "user1", "pass1", null))
}
@Test
fun `ignore duplicate machine entries after first in headers map`() {
val content =
"""
machine foo.com login firstUser password firstPass
machine foo.com login secondUser password secondPass
"""
.trimIndent()
val entries = Netrc.parse(content)
val headersMap = Netrc.toHeadersMap(entries)
val expectedBasic =
"Basic " + Base64.getEncoder().encodeToString("firstUser:firstPass".toByteArray())
assertThat(headersMap["http{,s}://foo.com/**"])
.isEqualTo(mapOf("Authorization" to listOf(expectedBasic)))
}
@Test
fun `handle empty or comment-only content`() {
assertThat(Netrc.parse("")).isEmpty()
assertThat(Netrc.parse("# only comments\n# second line")).isEmpty()
assertThat(Netrc.toHeadersMap(emptyList())).isEmpty()
}
@Test
fun `discard default entry in headers map`() {
val content =
"""
default login defUser password defPass
machine foo.com login fooUser password fooPass
"""
.trimIndent()
val entries = Netrc.parse(content)
val headersMap = Netrc.toHeadersMap(entries)
assertThat(headersMap).doesNotContainKey("**")
assertThat(headersMap).containsKey("http{,s}://foo.com/**")
}
@Test
fun `handle unclosed quotes and invalid escapes`() {
assertThatThrownBy { Netrc.parse("machine foo.com login \"unclosed") }
.isInstanceOf(Netrc.NetrcParseException::class.java)
assertThatThrownBy { Netrc.parse("machine foo.com login \"invalid\\") }
.isInstanceOf(Netrc.NetrcParseException::class.java)
}
}