fix(markdown)!: greacefully handle malformed input

This commit is contained in:
alina sireneva
2025-02-21 00:26:41 +03:00
parent d7a9ffe702
commit 573fb014ef
2 changed files with 90 additions and 23 deletions

View File

@@ -308,17 +308,19 @@ function parse(
pos += 1
if (pos > text.length) {
throw new Error('Malformed PRE entity, expected LF after ```')
// malformed pre entity, treat as plain text
result += '```'
feed(language)
} else {
if (!('pre' in stacks)) stacks.pre = []
stacks.pre.push({
_: 'messageEntityPre',
offset: result.length,
length: 0,
language,
})
insidePre = true
}
if (!('pre' in stacks)) stacks.pre = []
stacks.pre.push({
_: 'messageEntityPre',
offset: result.length,
length: 0,
language,
})
insidePre = true
} else {
pos += 1
if (!('code' in stacks)) stacks.code = []
@@ -442,9 +444,55 @@ function parse(
feed(strings[strings.length - 1])
function adjustOffsets(from: number, by: number): void {
for (const ent of entities) {
if (ent.offset >= from) ent.offset += by
}
for (const stack of Object.values(stacks)) {
for (const ent of stack) {
if (ent.offset >= from) ent.offset += by
}
}
}
for (const [name, stack] of Object.entries(stacks)) {
if (stack.length > 1) {
// todo: is this even possible?
throw new Error(`Malformed ${name} entity`)
}
if (stack.length) {
throw new Error(`Unterminated ${name} entity`)
// unterminated entity
switch (name) {
case 'link': {
const startOffset = stack.pop()!.offset
insideLink = false
insideLinkUrl = false
const url = pendingLinkUrl
pendingLinkUrl = ''
result = `${result.substring(0, startOffset)}[${result.substring(startOffset)}](`
adjustOffsets(startOffset, 1)
feed(url)
break
}
default: {
const startOffset = stack.pop()!.offset
const tag = {
Bold: TAG_BOLD,
Italic: TAG_ITALIC,
Underline: TAG_UNDERLINE,
Strike: TAG_STRIKE,
Spoiler: TAG_SPOILER,
code: TAG_CODE,
}[name]
if (!tag) throw new Error(`invalid tag ${name}`) // should never happen
const remaining = result.substring(startOffset)
result = `${result.substring(0, startOffset)}${tag}`
adjustOffsets(startOffset, tag.length)
feed(remaining)
break
}
}
}
}
@@ -454,9 +502,6 @@ function parse(
}
}
// typedoc doesn't support this yet, so we'll have to do it manually
// https://github.com/TypeStrong/typedoc/issues/2436
export const md: {
/**
* Tagged template based Markdown-to-entities parser function

View File

@@ -481,20 +481,42 @@ describe('MarkdownMessageEntityParser', () => {
})
describe('malformed input', () => {
const testThrows = (input: string) => expect(() => md_(input)).throws(Error)
it('should treat malformed links as plain text', () => {
test(
'plain [link](https://google.com but unclosed',
[],
'plain [link](https://google.com but unclosed',
)
it('should throw an error on malformed links', () => {
testThrows('plain [link](https://google.com but unclosed')
test(
'plain [**bold link**](https://google.com but __unclosed__',
[createEntity('messageEntityBold', 7, 9), createEntity('messageEntityItalic', 41, 8)],
'plain [bold link](https://google.com but unclosed',
)
})
it('should throw an error on malformed pres', () => {
testThrows('plain ```pre without linebreaks```')
testThrows('plain ``` pre without linebreaks but with spaces instead ```')
it('should ignore malformed pres', () => {
test('plain ```pre without linebreaks```', [], 'plain ```pre without linebreaks```')
test('plain ``` pre without linebreaks but with spaces instead ```', [], 'plain ``` pre without linebreaks but with spaces instead ```')
})
it('should throw an error on unterminated entity', () => {
testThrows('plain **bold but unclosed')
testThrows('plain **bold and __also italic but unclosed')
it('should ignore unterminated entities', () => {
test('plain **bold but unclosed', [], 'plain **bold but unclosed')
test('meow**', [], 'meow**')
test('meow**__', [], 'meow**__')
test('meow`woof', [], 'meow`woof')
test('meow```woof', [], 'meow```woof')
test(
'meow```woof **bold**',
[createEntity('messageEntityBold', 12, 4)],
'meow```woof bold',
)
test('plain **bold and __also italic but unclosed', [], 'plain **bold and __also italic but unclosed')
test(
'plain **bold and __italic__',
[createEntity('messageEntityItalic', 17, 6)],
'plain **bold and italic',
)
})
})
})