From 5096f837863a63abda5fb0204e251ba286d11acc Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 8 Jun 2024 16:31:37 +0300 Subject: [PATCH] fix: Require indentation for ? explicit-key contents (fixes #551) Adds `explicitKey?: true` to CST BlockMap items --- src/parse/cst.ts | 9 ++++++++- src/parse/parser.ts | 21 +++++++++------------ tests/doc/parse.ts | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/parse/cst.ts b/src/parse/cst.ts index 1d5c97e2..82b66dee 100644 --- a/src/parse/cst.ts +++ b/src/parse/cst.ts @@ -81,9 +81,16 @@ export interface BlockMap { offset: number indent: number items: Array< - | { start: SourceToken[]; key?: never; sep?: never; value?: never } | { start: SourceToken[] + explicitKey?: true + key?: never + sep?: never + value?: never + } + | { + start: SourceToken[] + explicitKey?: true key: Token | null sep: SourceToken[] value?: Token diff --git a/src/parse/parser.ts b/src/parse/parser.ts index fb1453b4..af09b527 100644 --- a/src/parse/parser.ts +++ b/src/parse/parser.ts @@ -325,7 +325,7 @@ export class Parser { it.value = token } else { Object.assign(it, { key: token, sep: [] }) - this.onKeyLine = !includesToken(it.start, 'explicit-key-ind') + this.onKeyLine = !it.explicitKey return } break @@ -532,7 +532,7 @@ export class Parser { const atNextItem = !this.onKeyLine && this.indent === map.indent && - it.sep && + (it.sep || it.explicitKey) && this.type !== 'seq-item-ind' // For empty nodes, assign newline-separated not indented empty tokens to following node @@ -572,24 +572,25 @@ export class Parser { return case 'explicit-key-ind': - if (!it.sep && !includesToken(it.start, 'explicit-key-ind')) { + if (!it.sep && !it.explicitKey) { it.start.push(this.sourceToken) + it.explicitKey = true } else if (atNextItem || it.value) { start.push(this.sourceToken) - map.items.push({ start }) + map.items.push({ start, explicitKey: true }) } else { this.stack.push({ type: 'block-map', offset: this.offset, indent: this.indent, - items: [{ start: [this.sourceToken] }] + items: [{ start: [this.sourceToken], explicitKey: true }] }) } this.onKeyLine = true return case 'map-value-ind': - if (includesToken(it.start, 'explicit-key-ind')) { + if (it.explicitKey) { if (!it.sep) { if (includesToken(it.start, 'newline')) { Object.assign(it, { key: null, sep: [this.sourceToken] }) @@ -672,11 +673,7 @@ export class Parser { default: { const bv = this.startBlockValue(map) if (bv) { - if ( - atNextItem && - bv.type !== 'block-seq' && - includesToken(it.start, 'explicit-key-ind') - ) { + if (atNextItem && bv.type !== 'block-seq' && it.explicitKey) { map.items.push({ start }) } this.stack.push(bv) @@ -888,7 +885,7 @@ export class Parser { type: 'block-map', offset: this.offset, indent: this.indent, - items: [{ start }] + items: [{ start, explicitKey: true }] } as BlockMap } case 'map-value-ind': { diff --git a/tests/doc/parse.ts b/tests/doc/parse.ts index 1a319325..a6af6764 100644 --- a/tests/doc/parse.ts +++ b/tests/doc/parse.ts @@ -409,6 +409,21 @@ describe('maps with no values', () => { }) }) +describe('odd indentations', () => { + test('Block map with empty explicit key (#551)', () => { + const doc = YAML.parseDocument('?\n? a') + expect(doc.contents.items).toMatchObject([ + { key: { value: null }, value: null }, + { key: { value: 'a' }, value: null } + ]) + }) + + test('Block map with unindented !!null explicit key', () => { + const doc = YAML.parseDocument('?\n!!null') + expect(doc.errors).not.toHaveLength(0) + }) +}) + describe('Excessive entity expansion attacks', () => { const root = resolve(__dirname, '../artifacts/pr104') const src1 = readFileSync(resolve(root, 'case1.yml'), 'utf8')