Skip to content

Commit 32243d4

Browse files
fix tag URI decoding and event conformance
Decode %xx escapes as UTF-8 during tag-handle expansion, reject malformed/invalid UTF-8 escapes, and preserve unknown expanded tags in representation comparisons. Update conformance expectations so event surface has no policy differences and keep JSON policy cases explicitly enumerated. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent b4bc395 commit 32243d4

6 files changed

Lines changed: 130 additions & 15 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ commit `45db50ae`). The pinned archive contains 402 inputs:
170170
- all 94 fixtures marked invalid are rejected;
171171
- 306 of 308 fixtures marked valid are accepted;
172172
- the other two valid-syntax fixtures, `2JQS` and `X38W`, are deliberately
173-
rejected because this module rejects duplicate mapping keys;
173+
rejected as a load/composition policy after syntactic recognition because this
174+
module rejects duplicate mapping keys in the representation graph;
174175
- 282 fixtures include `in.json`; three belong to invalid inputs, 277 of the
175176
279 applicable constructions match exactly, and two use a different
176177
documented projection policy.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
function ConvertFrom-YamlTagUriEscape {
2+
<#
3+
.SYNOPSIS
4+
Decodes YAML tag URI %xx escapes as UTF-8.
5+
#>
6+
[CmdletBinding()]
7+
[OutputType([string])]
8+
param (
9+
[Parameter(Mandatory)]
10+
[string] $Text,
11+
12+
[Parameter(Mandatory)]
13+
[pscustomobject] $Mark,
14+
15+
[Parameter(Mandatory)]
16+
[string] $Token
17+
)
18+
19+
function Get-YamlHexNibble {
20+
param ([char] $Character)
21+
if ($Character -notmatch '^[0-9A-Fa-f]$') {
22+
return -1
23+
}
24+
[System.Convert]::ToInt32([string] $Character, 16)
25+
}
26+
27+
$builder = [System.Text.StringBuilder]::new()
28+
$escapedBytes = [System.Collections.Generic.List[byte]]::new()
29+
$utf8 = [System.Text.UTF8Encoding]::new($false, $true)
30+
31+
for ($index = 0; $index -lt $Text.Length; $index++) {
32+
$character = $Text[$index]
33+
if ($character -ne '%') {
34+
if ($escapedBytes.Count -gt 0) {
35+
try {
36+
[void] $builder.Append($utf8.GetString($escapedBytes.ToArray()))
37+
} catch [System.Text.DecoderFallbackException] {
38+
throw (New-YamlException -Start $Mark -End $Mark -ErrorId 'YamlInvalidTag' -Message (
39+
"The tag token '$Token' contains an invalid UTF-8 escape sequence."
40+
))
41+
}
42+
$escapedBytes.Clear()
43+
}
44+
[void] $builder.Append($character)
45+
continue
46+
}
47+
48+
if ($index + 2 -ge $Text.Length) {
49+
throw (New-YamlException -Start $Mark -End $Mark -ErrorId 'YamlInvalidTag' -Message (
50+
"The tag token '$Token' contains a malformed percent escape."
51+
))
52+
}
53+
$high = Get-YamlHexNibble -Character $Text[$index + 1]
54+
$low = Get-YamlHexNibble -Character $Text[$index + 2]
55+
if ($high -lt 0 -or $low -lt 0) {
56+
throw (New-YamlException -Start $Mark -End $Mark -ErrorId 'YamlInvalidTag' -Message (
57+
"The tag token '$Token' contains a malformed percent escape."
58+
))
59+
}
60+
$escapedBytes.Add([byte](($high * 16) + $low))
61+
$index += 2
62+
}
63+
64+
if ($escapedBytes.Count -gt 0) {
65+
try {
66+
[void] $builder.Append($utf8.GetString($escapedBytes.ToArray()))
67+
} catch [System.Text.DecoderFallbackException] {
68+
throw (New-YamlException -Start $Mark -End $Mark -ErrorId 'YamlInvalidTag' -Message (
69+
"The tag token '$Token' contains an invalid UTF-8 escape sequence."
70+
))
71+
}
72+
}
73+
74+
$builder.ToString()
75+
}

src/functions/private/Resolve-YamlTag.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ function Resolve-YamlTag {
6767
}
6868
}
6969

70-
$expandedLength = $prefix.Length + $suffix.Length
70+
$expanded = ConvertFrom-YamlTagUriEscape -Text ($prefix + $suffix) -Mark $Mark -Token $Token
71+
$expandedLength = $expanded.Length
7172
if ($expandedLength -gt $Context.MaxTagLength) {
7273
throw (New-YamlException -Start $Mark -End $Mark -ErrorId 'YamlTagLimitExceeded' -Message (
7374
"A YAML tag exceeds the configured limit of $($Context.MaxTagLength) characters."
@@ -80,7 +81,6 @@ function Resolve-YamlTag {
8081
))
8182
}
8283

83-
$expanded = $prefix + $suffix
8484
$known = $expanded -cin @(
8585
'tag:yaml.org,2002:binary',
8686
'tag:yaml.org,2002:bool',

tests/Conformance.Tests.ps1

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,12 @@ Describe 'Released yaml-test-suite corpus accounting' {
4848
}
4949

5050
It 'accounts for parser representation/event comparisons' {
51-
@($suiteResults | Where-Object EventResult -EQ 'Pass').Count | Should -Be 307
51+
@($suiteResults | Where-Object EventResult -EQ 'Pass').Count | Should -Be 308
5252
@($suiteResults | Where-Object EventResult -EQ 'PolicyDifference').Count |
53-
Should -Be 1
53+
Should -Be 0
5454
@($suiteResults | Where-Object EventResult -EQ 'Fail').Count | Should -Be 0
5555
@($suiteResults | Where-Object EventResult -EQ 'NotApplicable').Count |
5656
Should -Be 94
57-
@(
58-
$suiteResults |
59-
Where-Object EventResult -EQ 'PolicyDifference' |
60-
Select-Object -ExpandProperty Case |
61-
Sort-Object
62-
) | Should -Be @('6CK3')
6357
}
6458

6559
It 'accounts for JSON construction comparisons' {
@@ -69,6 +63,12 @@ Describe 'Released yaml-test-suite corpus accounting' {
6963
@($suiteResults | Where-Object JsonResult -EQ 'Fail').Count | Should -Be 0
7064
@($suiteResults | Where-Object JsonResult -EQ 'NotApplicable').Count |
7165
Should -Be 123
66+
@(
67+
$suiteResults |
68+
Where-Object JsonResult -EQ 'PolicyDifference' |
69+
Select-Object -ExpandProperty Case |
70+
Sort-Object
71+
) | Should -Be @('565N', 'J7PZ')
7272
}
7373

7474
It 'accounts for out.yaml representation comparisons' {

tests/ConvertFrom-Yaml.Tests.ps1

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,49 @@ date: !!timestamp 2001-12-14
363363
{ $largeInteger | ConvertFrom-Yaml -MaxNumericLength 4096 } | Should -Throw
364364
}
365365

366+
It 'decodes percent escapes in expanded tags using UTF-8' {
367+
$yaml = @'
368+
%TAG !e! tag:example.com,2000:app/
369+
---
370+
first: !e!tag%21 value
371+
second: !e!currency%E2%82%AC amount
372+
'@
373+
$representation = Read-YamlStreamCore -Yaml $yaml -Depth 32 -MaxNodes 64 -MaxAliases 16 `
374+
-MaxScalarLength 4096 -MaxTagLength 1024 -MaxTotalTagLength 4096 `
375+
-MaxNumericLength 64
376+
377+
$representation.Value.Count | Should -Be 1
378+
$entries = $representation.Value[0].Entries
379+
$entries.Count | Should -Be 2
380+
$entries[0].Value.Tag | Should -Be 'tag:example.com,2000:app/tag!'
381+
$entries[1].Value.Tag | Should -Be ('tag:example.com,2000:app/currency' + [char] 0x20AC)
382+
}
383+
384+
It 'rejects malformed or non-UTF8 tag percent escapes' {
385+
$truncated = @'
386+
%TAG !e! tag:example.com,2000:app/
387+
---
388+
!e!tag%2 value
389+
'@
390+
$invalidHex = @'
391+
%TAG !e! tag:example.com,2000:app/
392+
---
393+
!e!tag%ZZ value
394+
'@
395+
$invalidUtf8 = @'
396+
%TAG !e! tag:example.com,2000:app/
397+
---
398+
!e!tag%E2%28%A1 value
399+
'@
400+
401+
($truncated | Test-Yaml) | Should -BeFalse
402+
($invalidHex | Test-Yaml) | Should -BeFalse
403+
($invalidUtf8 | Test-Yaml) | Should -BeFalse
404+
{ $truncated | ConvertFrom-Yaml } | Should -Throw
405+
{ $invalidHex | ConvertFrom-Yaml } | Should -Throw
406+
{ $invalidUtf8 | ConvertFrom-Yaml } | Should -Throw
407+
}
408+
366409
It 'bounds expanded-tag storage and rejects huge numerics promptly' {
367410
$prefix = 'x' * 20000
368411
$taggedItems = 1..500 | ForEach-Object { '- !e!value item' }

tests/tools/Invoke-YamlTestSuite.ps1

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,6 @@ function Get-YamlSuitePolicyReason {
259259
if ($YamlText -cmatch '!!(?:binary|omap|pairs|set|timestamp)(?:[ \t\r\n,\[\]\{\}]|$)') {
260260
return 'StandardTagProjectionPolicy'
261261
}
262-
if ($YamlText -cmatch '(?:^|[ \t\r\n\[\]\{\},])!<(?!tag:yaml\.org,2002:)' -or
263-
$YamlText -cmatch '(?:^|[ \t\r\n\[\]\{\},])![^\s!<][^\s:,\]\}\{]*') {
264-
return 'UnknownTagProjectionPolicy'
265-
}
266262
if (($Expected -match 'unsupported:' -or $Actual -match 'unsupported:')) {
267263
return 'PowerShellTypePolicy'
268264
}

0 commit comments

Comments
 (0)