Skip to content

Commit 1af06a9

Browse files
Count implicit keys by Unicode scalar
Apply the 1024-character limit with System.Text.Rune, cover plain and quoted boundaries, and retain the official multiline flow-mapping grammar while rejecting multiline flow-sequence pairs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent a580b6f commit 1af06a9

6 files changed

Lines changed: 99 additions & 12 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function Get-YamlImplicitKeyLength {
2+
<#
3+
.SYNOPSIS
4+
Gets an implicit key length in Unicode scalar values.
5+
#>
6+
[CmdletBinding()]
7+
[OutputType([int])]
8+
param (
9+
[Parameter(Mandatory)]
10+
[pscustomobject] $Node,
11+
12+
[Parameter(Mandatory)]
13+
[pscustomobject] $Context
14+
)
15+
16+
if ($Node.Kind.Equals('Scalar', [System.StringComparison]::Ordinal)) {
17+
return Get-YamlRuneCount -Text ([string] $Node.Value)
18+
}
19+
20+
$sourceLength = [Math]::Max(0, $Node.End.Index - $Node.Start.Index)
21+
return Get-YamlRuneCount -Text $Context.Text.Substring($Node.Start.Index, $sourceLength)
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function Get-YamlRuneCount {
2+
<#
3+
.SYNOPSIS
4+
Counts Unicode scalar values in validated YAML text.
5+
#>
6+
[CmdletBinding()]
7+
[OutputType([int])]
8+
param (
9+
[Parameter(Mandatory)]
10+
[AllowEmptyString()]
11+
[string] $Text
12+
)
13+
14+
$count = 0
15+
$index = 0
16+
while ($index -lt $Text.Length) {
17+
$rune = [System.Text.Rune]::GetRuneAt($Text, $index)
18+
$index += $rune.Utf16SequenceLength
19+
$count++
20+
}
21+
return $count
22+
}

src/functions/private/Read-YamlBlockMapping.ps1

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,6 @@ function Read-YamlBlockMapping {
182182
'A block mapping entry is missing its value indicator.'
183183
))
184184
}
185-
if ($colon -gt 1024) {
186-
$mark = New-YamlMark -Index ($Context.LineStarts[$lineNumber] + $contentColumn + $colon) `
187-
-Line $lineNumber -Column ($contentColumn + $colon)
188-
throw (New-YamlException -Start $mark -End $mark -ErrorId 'YamlInvalidImplicitKey' -Message (
189-
'An implicit mapping key cannot exceed 1024 characters.'
190-
))
191-
}
192-
193185
if ($colon -eq 0) {
194186
$mark = New-YamlMark -Index ($Context.LineStarts[$lineNumber] + $contentColumn) `
195187
-Line $lineNumber -Column $contentColumn
@@ -199,6 +191,13 @@ function Read-YamlBlockMapping {
199191
$key = Read-YamlBlockKey -Context $Context -Text $keyText -Line $lineNumber `
200192
-Column $contentColumn -Depth ($Depth + 1)
201193
}
194+
if ((Get-YamlImplicitKeyLength -Node $key -Context $Context) -gt 1024) {
195+
$mark = New-YamlMark -Index ($Context.LineStarts[$lineNumber] + $contentColumn + $colon) `
196+
-Line $lineNumber -Column ($contentColumn + $colon)
197+
throw (New-YamlException -Start $mark -End $mark -ErrorId 'YamlInvalidImplicitKey' -Message (
198+
'An implicit mapping key cannot exceed 1024 Unicode scalar values.'
199+
))
200+
}
202201

203202
$valueStart = $colon + 1
204203
$valueSource = $content.Substring($valueStart)

src/functions/private/Read-YamlFlowNode.ps1

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,11 @@ function Read-YamlFlowNode {
190190
if ($Cursor.Index -lt $Context.Text.Length -and $Context.Text[$Cursor.Index] -eq ':') {
191191
if (-not $explicitPair -and (
192192
$Cursor.Line -ne $itemStartLine -or
193-
$Cursor.Index - $item.Start.Index -gt 1024
193+
(Get-YamlImplicitKeyLength -Node $item -Context $Context) -gt 1024
194194
)) {
195195
$mark = New-YamlMark -Index $Cursor.Index -Line $Cursor.Line -Column $Cursor.Column
196196
throw (New-YamlException -Start $mark -End $mark -ErrorId 'YamlInvalidImplicitKey' -Message (
197-
'An implicit mapping key must fit on one line and cannot exceed 1024 characters.'
197+
'An implicit mapping key must fit on one line and cannot exceed 1024 Unicode scalar values.'
198198
))
199199
}
200200
$pair = New-YamlSyntaxNode -Context $Context -Kind Mapping -Depth ($Depth + 1) `
@@ -269,10 +269,12 @@ function Read-YamlFlowNode {
269269
}
270270
}
271271
Skip-YamlFlowTrivia -Cursor $Cursor -Context $Context
272-
if (-not $explicit -and $Cursor.Index - $key.Start.Index -gt 1024) {
272+
if (-not $explicit -and (
273+
(Get-YamlImplicitKeyLength -Node $key -Context $Context) -gt 1024
274+
)) {
273275
$mark = New-YamlMark -Index $Cursor.Index -Line $Cursor.Line -Column $Cursor.Column
274276
throw (New-YamlException -Start $mark -End $mark -ErrorId 'YamlInvalidImplicitKey' -Message (
275-
'An implicit mapping key must fit on one line and cannot exceed 1024 characters.'
277+
'An implicit mapping key must fit on one line and cannot exceed 1024 Unicode scalar values.'
276278
))
277279
}
278280
if ($Cursor.Index -ge $Context.Text.Length -or $Context.Text[$Cursor.Index] -ne ':') {

tests/ConvertFrom-Yaml.Tests.ps1

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,17 @@ folded: >
153153

154154
$result | Should -Be @('foo bar', "foo`nbar")
155155
}
156+
157+
It 'accepts adjacent flow collection values after a colon' {
158+
$sequenceValue = '{foo:[bar]}' | ConvertFrom-Yaml
159+
$mappingValue = '{foo:{bar: baz}}' | ConvertFrom-Yaml
160+
$sequencePair = '[foo:[bar]]' | ConvertFrom-Yaml -NoEnumerate
161+
162+
$sequenceValue.foo | Should -Be @('bar')
163+
$mappingValue.foo.bar | Should -Be 'baz'
164+
$sequencePair.Count | Should -Be 1
165+
$sequencePair[0].foo | Should -Be @('bar')
166+
}
156167
}
157168

158169
Context 'Streams and pipeline input' {

tests/Test-Yaml.Tests.ps1

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,37 @@ Describe 'Test-Yaml' {
3737
("a: &a value`nb: !foo *a" | Test-Yaml) | Should -BeFalse
3838
}
3939

40+
It 'enforces implicit-key line and Unicode scalar limits' {
41+
$emoji = [char]::ConvertFromUtf32(0x1F600)
42+
43+
("['multi`n line': value]" | Test-Yaml) | Should -BeFalse
44+
("[multi`n line: value]" | Test-Yaml) | Should -BeFalse
45+
46+
foreach ($quoted in @($false, $true)) {
47+
foreach ($length in @(1024, 1025)) {
48+
$text = 'k' * $length
49+
$key = if ($quoted) { "'$text'" } else { $text }
50+
foreach ($yaml in @(
51+
"[$key`: value]",
52+
"{$key`: value}"
53+
)) {
54+
($yaml | Test-Yaml) | Should -Be ($length -eq 1024)
55+
}
56+
}
57+
}
58+
59+
foreach ($length in @(513, 1024, 1025)) {
60+
$key = $emoji * $length
61+
("{$key`: value}" | Test-Yaml) |
62+
Should -Be ($length -le 1024)
63+
}
64+
}
65+
66+
It 'accepts multiline keys in flow mappings' {
67+
("{'multi`n line': value}" | Test-Yaml) | Should -BeTrue
68+
("{multi`n line: value}" | Test-Yaml) | Should -BeTrue
69+
}
70+
4071
It 'validates tag directives, tag tokens, and alias properties' {
4172
("%TAG !! tag:example.com,2000:app/`n---`n!!int value" | Test-Yaml) |
4273
Should -BeTrue

0 commit comments

Comments
 (0)