diff --git a/src/commonMain/kotlin/com/charleskorn/kaml/YamlConfiguration.kt b/src/commonMain/kotlin/com/charleskorn/kaml/YamlConfiguration.kt index 6815f2ec..e3f76075 100644 --- a/src/commonMain/kotlin/com/charleskorn/kaml/YamlConfiguration.kt +++ b/src/commonMain/kotlin/com/charleskorn/kaml/YamlConfiguration.kt @@ -33,6 +33,7 @@ package com.charleskorn.kaml * * [sequenceStyle]: how sequences (aka lists and arrays) should be formatted. See [SequenceStyle] for an example of each * * [ambiguousQuoteStyle]: how strings should be escaped when [singleLineStringStyle] is [SingleLineStringStyle.PlainExceptAmbiguous] and the value is ambiguous * * [sequenceBlockIndent]: number of spaces to use as indentation for sequences, if [sequenceStyle] set to [SequenceStyle.Block] + * * [allowAnchorsAndAliases]: set to true to allow anchors and aliases when decoding YAML (defaults to `false`) */ public data class YamlConfiguration constructor( internal val encodeDefaults: Boolean = true, @@ -47,6 +48,7 @@ public data class YamlConfiguration constructor( internal val multiLineStringStyle: MultiLineStringStyle = singleLineStringStyle.multiLineStringStyle, internal val ambiguousQuoteStyle: AmbiguousQuoteStyle = AmbiguousQuoteStyle.DoubleQuoted, internal val sequenceBlockIndent: Int = 0, + internal val allowAnchorsAndAliases: Boolean = false, ) public enum class PolymorphismStyle { diff --git a/src/commonMain/kotlin/com/charleskorn/kaml/YamlException.kt b/src/commonMain/kotlin/com/charleskorn/kaml/YamlException.kt index 1a758ee8..0546c208 100644 --- a/src/commonMain/kotlin/com/charleskorn/kaml/YamlException.kt +++ b/src/commonMain/kotlin/com/charleskorn/kaml/YamlException.kt @@ -98,3 +98,5 @@ public class NoAnchorForExtensionException( path: YamlPath, ) : YamlException("The key '$key' starts with the extension definition prefix '$extensionDefinitionPrefix' but does not define an anchor.", path) + +public class ForbiddenAnchorOrAliasException(message: String, path: YamlPath) : YamlException(message, path) diff --git a/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt b/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt index 4fefab4d..14fd188b 100644 --- a/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt +++ b/src/commonTest/kotlin/com/charleskorn/kaml/YamlReadingTest.kt @@ -1170,8 +1170,23 @@ class YamlReadingTest : DescribeSpec({ name: *name """.trimIndent() - context("parsing that input") { - val configuration = YamlConfiguration(extensionDefinitionPrefix = ".") + context("parsing anchors and aliases is disabled") { + val configuration = YamlConfiguration(extensionDefinitionPrefix = ".", allowAnchorsAndAliases = false) + val yaml = Yaml(configuration = configuration) + + it("throws an appropriate exception") { + val exception = shouldThrow { yaml.decodeFromString(SimpleStructure.serializer(), input) } + + exception.asClue { + it.message shouldBe "Parsing anchors and aliases is disabled." + it.line shouldBe 1 + it.column shouldBe 18 + } + } + } + + context("parsing anchors and aliases is enabled") { + val configuration = YamlConfiguration(extensionDefinitionPrefix = ".", allowAnchorsAndAliases = true) val yaml = Yaml(configuration = configuration) val result = yaml.decodeFromString(SimpleStructure.serializer(), input) diff --git a/src/jvmMain/kotlin/com/charleskorn/kaml/Yaml.kt b/src/jvmMain/kotlin/com/charleskorn/kaml/Yaml.kt index 16773f4f..3ad3817b 100644 --- a/src/jvmMain/kotlin/com/charleskorn/kaml/Yaml.kt +++ b/src/jvmMain/kotlin/com/charleskorn/kaml/Yaml.kt @@ -60,7 +60,7 @@ public actual class Yaml( private fun parseToYamlNodeFromReader(source: Reader): YamlNode { val parser = YamlParser(source) - val reader = YamlNodeReader(parser, configuration.extensionDefinitionPrefix) + val reader = YamlNodeReader(parser, configuration.extensionDefinitionPrefix, configuration.allowAnchorsAndAliases) val node = reader.read() parser.ensureEndOfStreamReached() return node diff --git a/src/jvmMain/kotlin/com/charleskorn/kaml/YamlNodeReader.kt b/src/jvmMain/kotlin/com/charleskorn/kaml/YamlNodeReader.kt index 3ed66521..5f5c4498 100644 --- a/src/jvmMain/kotlin/com/charleskorn/kaml/YamlNodeReader.kt +++ b/src/jvmMain/kotlin/com/charleskorn/kaml/YamlNodeReader.kt @@ -30,6 +30,7 @@ import java.util.Optional internal actual class YamlNodeReader( private val parser: YamlParser, private val extensionDefinitionPrefix: String? = null, + private val allowAnchorsAndAliases: Boolean = false, ) { private val aliases = mutableMapOf() @@ -43,6 +44,10 @@ internal actual class YamlNodeReader( if (event is NodeEvent) { event.anchor.ifPresent { + if (!allowAnchorsAndAliases) { + throw ForbiddenAnchorOrAliasException("Parsing anchors and aliases is disabled.", path) + } + aliases.put(it, node.withPath(YamlPath.forAliasDefinition(it.value, event.location))) } @@ -186,6 +191,10 @@ internal actual class YamlNodeReader( } private fun readAlias(event: AliasEvent, path: YamlPath): YamlNode { + if (!allowAnchorsAndAliases) { + throw ForbiddenAnchorOrAliasException("Parsing anchors and aliases is disabled.", path) + } + val anchor = event.anchor.get() val resolvedNode = aliases.getOrElse(anchor) { diff --git a/src/jvmTest/kotlin/com/charleskorn/kaml/YamlNodeReaderTest.kt b/src/jvmTest/kotlin/com/charleskorn/kaml/YamlNodeReaderTest.kt index c93f1f44..f988badf 100644 --- a/src/jvmTest/kotlin/com/charleskorn/kaml/YamlNodeReaderTest.kt +++ b/src/jvmTest/kotlin/com/charleskorn/kaml/YamlNodeReaderTest.kt @@ -312,7 +312,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() it("returns the expected list") { result shouldBe @@ -339,7 +339,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() it("returns the expected list, using the most-recently defined value each time the alias is referenced") { result shouldBe @@ -363,11 +363,11 @@ class YamlNodeReaderTest : DescribeSpec({ - *thing """.trimIndent() - describe("parsing that input") { + describe("parsing that input with anchor and alias parsing enabled") { it("throws an appropriate exception") { val exception = shouldThrow { val parser = YamlParser(input) - YamlNodeReader(parser).read() + YamlNodeReader(parser, allowAnchorsAndAliases = true).read() } exception.asClue { @@ -378,6 +378,21 @@ class YamlNodeReaderTest : DescribeSpec({ } } } + + describe("parsing that input with anchor and alias parsing disabled") { + it("throws an appropriate exception") { + val exception = shouldThrow { + val parser = YamlParser(input) + YamlNodeReader(parser, allowAnchorsAndAliases = false).read() + } + + exception.asClue { + it.message shouldBe "Parsing anchors and aliases is disabled." + it.line shouldBe 2 + it.column shouldBe 3 + } + } + } } context("given some input representing a list of strings in flow style") { @@ -652,7 +667,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() val key1Path = YamlPath.root.withMapElementKey("key1", Location(1, 1)) val value1Path = key1Path.withMapElementValue(Location(1, 7)) val key2Path = YamlPath.root.withMapElementKey("key2", Location(2, 1)) @@ -1156,7 +1171,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3)) val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 13)) @@ -1206,7 +1221,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3)) val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 13)) @@ -1302,7 +1317,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3)) val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 13)) @@ -1365,7 +1380,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input") { val parser = YamlParser(input) - val result = YamlNodeReader(parser).read() + val result = YamlNodeReader(parser, allowAnchorsAndAliases = true).read() val firstItemPath = YamlPath.root.withListEntry(0, Location(1, 3)) val firstXPath = firstItemPath.withMapElementKey("x", Location(1, 11)) @@ -1531,7 +1546,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input with an extension definition prefix defined") { val parser = YamlParser(input) - val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".").read() + val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".", allowAnchorsAndAliases = true).read() val fooKeyPath = YamlPath.root.withMapElementKey("foo", Location(3, 1)) val fooValuePath = fooKeyPath.withMapElementValue(Location(4, 5)) @@ -1598,7 +1613,7 @@ class YamlNodeReaderTest : DescribeSpec({ describe("parsing that input with an extension definition prefix defined") { val parser = YamlParser(input) - val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".").read() + val result = YamlNodeReader(parser, extensionDefinitionPrefix = ".", allowAnchorsAndAliases = true).read() val keyPath = YamlPath.root .withMerge(Location(4, 6))