securemark 0.288.0 → 0.288.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/index.js +7325 -7398
- package/package.json +1 -1
- package/src/parser/block/codeblock.ts +9 -6
- package/src/parser/block/extension/aside.ts +7 -8
- package/src/parser/block/extension/example.ts +7 -8
- package/src/parser/block/extension/figure.ts +77 -74
- package/src/parser/block/extension/message.ts +7 -8
- package/src/parser/block/extension/placeholder.ts +6 -3
- package/src/parser/block/extension/table.ts +13 -24
- package/src/parser/block/heading.ts +2 -3
- package/src/parser/block/ilist.ts +17 -7
- package/src/parser/block/mathblock.ts +8 -6
- package/src/parser/block/mediablock.ts +5 -8
- package/src/parser/block/olist.ts +5 -7
- package/src/parser/block/reply/cite.ts +2 -6
- package/src/parser/block/reply/quote.ts +3 -2
- package/src/parser/block/reply.ts +1 -1
- package/src/parser/block/sidefence.ts +2 -3
- package/src/parser/block/table.ts +2 -3
- package/src/parser/block/ulist.ts +3 -17
- package/src/parser/header.ts +2 -3
- package/src/parser/inline/extension/placeholder.ts +2 -3
- package/src/parser/inline/html.ts +21 -23
- package/src/parser/inline/htmlentity.ts +2 -3
- package/src/parser/inline/link.test.ts +4 -0
- package/src/parser/inline/link.ts +3 -5
- package/src/parser/inline/math.ts +3 -3
- package/src/parser/inline/media.test.ts +17 -13
- package/src/parser/inline/media.ts +14 -8
- package/src/parser/inline/reference.ts +2 -6
- package/src/parser/inline/ruby.ts +2 -3
- package/src/parser/util.ts +12 -0
|
@@ -3,6 +3,7 @@ import { Recursion } from '../context';
|
|
|
3
3
|
import { union, some, recursion, block, focus, rewrite, convert, lazy, fmap } from '../../combinator';
|
|
4
4
|
import { autolink } from '../autolink';
|
|
5
5
|
import { contentline } from '../source';
|
|
6
|
+
import { invalid } from '../util';
|
|
6
7
|
import { html, define, defrag } from 'typed-dom/dom';
|
|
7
8
|
|
|
8
9
|
export const sidefence: SidefenceParser = lazy(() => block(fmap(focus(
|
|
@@ -11,9 +12,7 @@ export const sidefence: SidefenceParser = lazy(() => block(fmap(focus(
|
|
|
11
12
|
([el]) => [
|
|
12
13
|
define(el, {
|
|
13
14
|
class: 'invalid',
|
|
14
|
-
'
|
|
15
|
-
'data-invalid-type': 'syntax',
|
|
16
|
-
'data-invalid-message': 'Reserved syntax',
|
|
15
|
+
...invalid('sidefence', 'syntax', 'Reserved syntax'),
|
|
17
16
|
}),
|
|
18
17
|
])));
|
|
19
18
|
|
|
@@ -3,6 +3,7 @@ import { union, sequence, some, block, line, validate, focus, rewrite, surround,
|
|
|
3
3
|
import { inline, media, medialink, shortmedia } from '../inline';
|
|
4
4
|
import { contentline } from '../source';
|
|
5
5
|
import { trimBlank } from '../visibility';
|
|
6
|
+
import { invalid } from '../util';
|
|
6
7
|
import { duffReduce } from 'spica/duff';
|
|
7
8
|
import { push } from 'spica/array';
|
|
8
9
|
import { html, defrag } from 'typed-dom/dom';
|
|
@@ -31,9 +32,7 @@ const row = <P extends CellParser | AlignParser>(parser: P, optional: boolean):
|
|
|
31
32
|
rewrite(contentline, ({ source }) => [[
|
|
32
33
|
html('tr', {
|
|
33
34
|
class: 'invalid',
|
|
34
|
-
'
|
|
35
|
-
'data-invalid-type': 'syntax',
|
|
36
|
-
'data-invalid-message': 'Missing the start symbol of the table row',
|
|
35
|
+
...invalid('table-row', 'syntax', 'Missing the start symbol of the table row'),
|
|
37
36
|
}, [html('td', source.replace('\n', ''))])
|
|
38
37
|
], '']));
|
|
39
38
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { UListParser } from '../block';
|
|
2
|
-
import { Parser } from '../../combinator/data/parser';
|
|
3
2
|
import { Recursion } from '../context';
|
|
4
|
-
import { union, inits, subsequence, some, recursion, block, line, validate, indent, focus,
|
|
3
|
+
import { union, inits, subsequence, some, recursion, block, line, validate, indent, focus, open, trim, fallback, lazy, fmap } from '../../combinator';
|
|
5
4
|
import { olist_ } from './olist';
|
|
6
|
-
import { ilist_ } from './ilist';
|
|
5
|
+
import { ilist_, ilistitem } from './ilist';
|
|
7
6
|
import { inline, indexer, indexee, dataindex } from '../inline';
|
|
8
|
-
import { contentline } from '../source';
|
|
9
7
|
import { lineable } from '../util';
|
|
10
8
|
import { visualize, trimBlank } from '../visibility';
|
|
11
9
|
import { unshift } from 'spica/array';
|
|
@@ -26,7 +24,7 @@ export const ulist_: UListParser = lazy(() => block(fmap(validate(
|
|
|
26
24
|
]), true)),
|
|
27
25
|
indent(union([ulist_, olist_, ilist_])),
|
|
28
26
|
]),
|
|
29
|
-
|
|
27
|
+
ilistitem),
|
|
30
28
|
ns => [html('li', { 'data-index': dataindex(ns) }, defrag(fillFirstLine(ns)))])),
|
|
31
29
|
])))),
|
|
32
30
|
es => [format(html('ul', es))])));
|
|
@@ -37,18 +35,6 @@ export const checkbox = focus(
|
|
|
37
35
|
html('span', { class: 'checkbox' }, source[1].trimStart() ? '☑' : '☐'),
|
|
38
36
|
], ''], false);
|
|
39
37
|
|
|
40
|
-
export const invalid = rewrite(
|
|
41
|
-
inits([contentline, indent<Parser<string>>(({ source }) => [[source], ''])]),
|
|
42
|
-
({ source }) => [[
|
|
43
|
-
'',
|
|
44
|
-
html('span', {
|
|
45
|
-
class: 'invalid',
|
|
46
|
-
'data-invalid-syntax': 'list',
|
|
47
|
-
'data-invalid-type': 'syntax',
|
|
48
|
-
'data-invalid-message': 'Fix the indent or the head of the list item',
|
|
49
|
-
}, source.replace('\n', ''))
|
|
50
|
-
], '']);
|
|
51
|
-
|
|
52
38
|
export function fillFirstLine(ns: (HTMLElement | string)[]): (HTMLElement | string)[] {
|
|
53
39
|
return ns.length === 1
|
|
54
40
|
&& typeof ns[0] === 'object'
|
package/src/parser/header.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { MarkdownParser } from '../../markdown';
|
|
|
2
2
|
import { union, inits, some, block, line, validate, focus, rewrite, clear, convert, lazy, fmap } from '../combinator';
|
|
3
3
|
import { segment } from './segment';
|
|
4
4
|
import { str } from './source';
|
|
5
|
+
import { invalid } from './util';
|
|
5
6
|
import { normalize } from './api/normalize';
|
|
6
7
|
import { html, defrag } from 'typed-dom/dom';
|
|
7
8
|
|
|
@@ -31,9 +32,7 @@ export const header: MarkdownParser.HeaderParser = lazy(() => validate(
|
|
|
31
32
|
html('pre', {
|
|
32
33
|
class: 'invalid',
|
|
33
34
|
translate: 'no',
|
|
34
|
-
'
|
|
35
|
-
'data-invalid-type': 'syntax',
|
|
36
|
-
'data-invalid-message': 'Invalid syntax',
|
|
35
|
+
...invalid('header', 'syntax', 'Invalid syntax'),
|
|
37
36
|
}, normalize(source)),
|
|
38
37
|
], ''],
|
|
39
38
|
]))),
|
|
@@ -4,6 +4,7 @@ import { union, some, recursion, precedence, surround, lazy } from '../../../com
|
|
|
4
4
|
import { inline } from '../../inline';
|
|
5
5
|
import { str } from '../../source';
|
|
6
6
|
import { tightStart } from '../../visibility';
|
|
7
|
+
import { invalid } from '../../util';
|
|
7
8
|
import { unshift } from 'spica/array';
|
|
8
9
|
import { html, defrag } from 'typed-dom/dom';
|
|
9
10
|
|
|
@@ -19,9 +20,7 @@ export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => surroun
|
|
|
19
20
|
([, bs], rest) => [[
|
|
20
21
|
html('span', {
|
|
21
22
|
class: 'invalid',
|
|
22
|
-
|
|
23
|
-
'data-invalid-type': 'syntax',
|
|
24
|
-
'data-invalid-message': `Invalid start symbol or linebreak`,
|
|
23
|
+
...invalid('extension', 'syntax', `Invalid start symbol or linebreak`),
|
|
25
24
|
}, defrag(bs)),
|
|
26
25
|
], rest],
|
|
27
26
|
([as, bs], rest) => [unshift(as, bs), rest], [3 | Backtrack.bracket]));
|
|
@@ -4,6 +4,7 @@ import { union, subsequence, some, recursion, precedence, validate, focus, surro
|
|
|
4
4
|
import { inline } from '../inline';
|
|
5
5
|
import { str } from '../source';
|
|
6
6
|
import { isLooseNodeStart, blankWith } from '../visibility';
|
|
7
|
+
import { invalid } from '../util';
|
|
7
8
|
import { memoize } from 'spica/memoize';
|
|
8
9
|
import { Clock } from 'spica/clock';
|
|
9
10
|
import { unshift, push, splice } from 'spica/array';
|
|
@@ -216,23 +217,20 @@ const TAGS = Object.freeze([
|
|
|
216
217
|
function elem(tag: string, as: string[], bs: (HTMLElement | string)[], cs: string[]): HTMLElement {
|
|
217
218
|
assert(as.length > 0);
|
|
218
219
|
assert(as[0][0] === '<' && as.at(-1)!.slice(-1) === '>');
|
|
219
|
-
if (!tags.includes(tag)) return
|
|
220
|
-
if (cs.length === 0) return
|
|
221
|
-
if (bs.length === 0) return
|
|
222
|
-
if (!isLooseNodeStart(bs)) return
|
|
220
|
+
if (!tags.includes(tag)) return ielem('tag', `Invalid HTML tag name "${tag}"`, as, bs, cs);
|
|
221
|
+
if (cs.length === 0) return ielem('tag', `Missing the closing HTML tag "</${tag}>"`, as, bs, cs);
|
|
222
|
+
if (bs.length === 0) return ielem('content', `Missing the content`, as, bs, cs);
|
|
223
|
+
if (!isLooseNodeStart(bs)) return ielem('content', `Missing the visible content in the same line`, as, bs, cs);
|
|
223
224
|
const attrs = attributes('html', [], attrspecs[tag], as.slice(1, -1));
|
|
224
|
-
return
|
|
225
|
-
?
|
|
225
|
+
return /(?<!\S)invalid(?!\S)/.test(attrs['class'] ?? '')
|
|
226
|
+
? ielem('attribute', 'Invalid HTML attribute', as, bs, cs)
|
|
226
227
|
: h(tag as 'span', attrs, defrag(bs));
|
|
227
228
|
}
|
|
228
229
|
|
|
229
|
-
function
|
|
230
|
-
return h('span',
|
|
231
|
-
class: 'invalid',
|
|
232
|
-
|
|
233
|
-
'data-invalid-type': type,
|
|
234
|
-
'data-invalid-message': message,
|
|
235
|
-
}, defrag(push(unshift(as, bs), cs)));
|
|
230
|
+
function ielem(type: string, message: string, as: (HTMLElement | string)[], bs: (HTMLElement | string)[], cs: (HTMLElement | string)[]): HTMLElement {
|
|
231
|
+
return h('span',
|
|
232
|
+
{ class: 'invalid', ...invalid('html', type, message) },
|
|
233
|
+
defrag(push(unshift(as, bs), cs)));
|
|
236
234
|
}
|
|
237
235
|
|
|
238
236
|
const requiredAttributes = memoize(
|
|
@@ -249,7 +247,7 @@ export function attributes(
|
|
|
249
247
|
assert(spec instanceof Object === false);
|
|
250
248
|
assert(!spec?.['__proto__']);
|
|
251
249
|
assert(!spec?.toString);
|
|
252
|
-
let
|
|
250
|
+
let invalidation = false;
|
|
253
251
|
const attrs: Record<string, string | undefined> = {};
|
|
254
252
|
for (let i = 0; i < params.length; ++i) {
|
|
255
253
|
const param = params[i].trim();
|
|
@@ -257,20 +255,20 @@ export function attributes(
|
|
|
257
255
|
const value = param !== name
|
|
258
256
|
? param.slice(name.length + 2, -1).replace(/\\(.?)/g, '$1')
|
|
259
257
|
: undefined;
|
|
260
|
-
|
|
258
|
+
invalidation ||= !spec || name in attrs;
|
|
261
259
|
if (spec && name in spec && !spec[name]) continue;
|
|
262
|
-
spec?.[name]?.includes(value) ||
|
|
260
|
+
spec?.[name]?.includes(value) || spec?.[name]?.length === 0 && value !== undefined
|
|
263
261
|
? attrs[name] = value ?? ''
|
|
264
|
-
:
|
|
262
|
+
: invalidation ||= !!spec;
|
|
265
263
|
assert(!(name in {} && attrs.hasOwnProperty(name)));
|
|
266
264
|
splice(params, i--, 1);
|
|
267
265
|
}
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
attrs['class'] =
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
attrs
|
|
266
|
+
invalidation ||= !!spec && !requiredAttributes(spec).every(name => name in attrs);
|
|
267
|
+
if (invalidation) {
|
|
268
|
+
attrs['class'] = classes.length === 0
|
|
269
|
+
? 'invalid'
|
|
270
|
+
: `${classes.join(' ')}${classes.includes('invalid') ? '' : ' invalid'}`;
|
|
271
|
+
Object.assign(attrs, invalid(syntax, 'argument', 'Invalid argument'));
|
|
274
272
|
}
|
|
275
273
|
return attrs;
|
|
276
274
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HTMLEntityParser, UnsafeHTMLEntityParser } from '../inline';
|
|
2
2
|
import { Command } from '../context';
|
|
3
3
|
import { union, validate, focus, fmap } from '../../combinator';
|
|
4
|
+
import { invalid } from '../util';
|
|
4
5
|
import { html } from 'typed-dom/dom';
|
|
5
6
|
import { reduce } from 'spica/memoize';
|
|
6
7
|
|
|
@@ -14,9 +15,7 @@ export const htmlentity: HTMLEntityParser = fmap(
|
|
|
14
15
|
text[0] === Command.Escape
|
|
15
16
|
? html('span', {
|
|
16
17
|
class: 'invalid',
|
|
17
|
-
'
|
|
18
|
-
'data-invalid-type': 'syntax',
|
|
19
|
-
'data-invalid-message': 'Invalid HTML entity',
|
|
18
|
+
...invalid('htmlentity', 'syntax', 'Invalid HTML entity'),
|
|
20
19
|
}, text.slice(1))
|
|
21
20
|
: text,
|
|
22
21
|
]);
|
|
@@ -64,6 +64,8 @@ describe('Unit: parser/inline/link', () => {
|
|
|
64
64
|
assert.deepStrictEqual(inspect(parser('[]{ }')), undefined);
|
|
65
65
|
assert.deepStrictEqual(inspect(parser('[]{ }')), undefined);
|
|
66
66
|
assert.deepStrictEqual(inspect(parser('[]{{}')), undefined);
|
|
67
|
+
assert.deepStrictEqual(inspect(parser('[]{}}')), undefined);
|
|
68
|
+
assert.deepStrictEqual(inspect(parser('[]{{}}')), undefined);
|
|
67
69
|
assert.deepStrictEqual(inspect(parser('[]{{b}}')), undefined);
|
|
68
70
|
assert.deepStrictEqual(inspect(parser('[]{b\nb}')), undefined);
|
|
69
71
|
assert.deepStrictEqual(inspect(parser('[]{b\\\nb}')), undefined);
|
|
@@ -107,6 +109,8 @@ describe('Unit: parser/inline/link', () => {
|
|
|
107
109
|
assert.deepStrictEqual(inspect(parser('[]{ b }')), [['<a class="url" href="b">b</a>'], '']);
|
|
108
110
|
assert.deepStrictEqual(inspect(parser('[]{ b }')), [['<a class="url" href="b">b</a>'], '']);
|
|
109
111
|
assert.deepStrictEqual(inspect(parser('[]{ b }')), [['<a class="url" href="b">b</a>'], '']);
|
|
112
|
+
assert.deepStrictEqual(inspect(parser('[]{"}')), [[`<a class="url" href=""">"</a>`], '']);
|
|
113
|
+
assert.deepStrictEqual(inspect(parser('[]{"}"}')), [[`<a class="url" href=""">"</a>`], '"}']);
|
|
110
114
|
assert.deepStrictEqual(inspect(parser('[]{\\}')), [[`<a class="url" href="\\">\\</a>`], '']);
|
|
111
115
|
assert.deepStrictEqual(inspect(parser('[]{\\ }')), [[`<a class="url" href="\\">\\</a>`], '']);
|
|
112
116
|
assert.deepStrictEqual(inspect(parser('[]{\\b}')), [[`<a class="url" href="\\b">\\b</a>`], '']);
|
|
@@ -6,7 +6,7 @@ import { inline, media, shortmedia } from '../inline';
|
|
|
6
6
|
import { attributes } from './html';
|
|
7
7
|
import { linebreak, unescsource, str } from '../source';
|
|
8
8
|
import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
|
|
9
|
-
import { stringify } from '../util';
|
|
9
|
+
import { invalid, stringify } from '../util';
|
|
10
10
|
import { ReadonlyURL } from 'spica/url';
|
|
11
11
|
import { html, define, defrag } from 'typed-dom/dom';
|
|
12
12
|
|
|
@@ -94,7 +94,7 @@ function parse(
|
|
|
94
94
|
content,
|
|
95
95
|
uri,
|
|
96
96
|
context.host?.origin || location.origin);
|
|
97
|
-
return el.
|
|
97
|
+
return el.classList.contains('invalid')
|
|
98
98
|
? el
|
|
99
99
|
: define(el, attributes('link', [], optspec, params));
|
|
100
100
|
}
|
|
@@ -166,9 +166,7 @@ function elem(
|
|
|
166
166
|
return html('a',
|
|
167
167
|
{
|
|
168
168
|
class: 'invalid',
|
|
169
|
-
|
|
170
|
-
'data-invalid-type': type ??= 'argument',
|
|
171
|
-
'data-invalid-message': message ??= 'Invalid protocol',
|
|
169
|
+
...invalid('link', type ??= 'argument', message ??= 'Invalid protocol'),
|
|
172
170
|
},
|
|
173
171
|
content.length === 0
|
|
174
172
|
? INSECURE_URI
|
|
@@ -2,6 +2,7 @@ import { MathParser } from '../inline';
|
|
|
2
2
|
import { Recursion } from '../context';
|
|
3
3
|
import { union, some, recursion, precedence, validate, focus, rewrite, surround, lazy } from '../../combinator';
|
|
4
4
|
import { escsource, unescsource, str } from '../source';
|
|
5
|
+
import { invalid } from '../util';
|
|
5
6
|
import { html } from 'typed-dom/dom';
|
|
6
7
|
|
|
7
8
|
const forbiddenCommand = /\\(?:begin|tiny|huge|large)(?![a-z])/i;
|
|
@@ -25,9 +26,8 @@ export const math: MathParser = lazy(() => validate('$', rewrite(
|
|
|
25
26
|
: {
|
|
26
27
|
class: 'invalid',
|
|
27
28
|
translate: 'no',
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'data-invalid-message': `"${source.match(forbiddenCommand)![0]}" command is forbidden`,
|
|
29
|
+
...invalid('math', 'content',
|
|
30
|
+
`"${source.match(forbiddenCommand)![0]}" command is forbidden`),
|
|
31
31
|
},
|
|
32
32
|
source)
|
|
33
33
|
], ''])));
|
|
@@ -7,10 +7,10 @@ describe('Unit: parser/inline/media', () => {
|
|
|
7
7
|
const parser = (source: string) => some(media)({ source, context: {} });
|
|
8
8
|
|
|
9
9
|
it('xss', () => {
|
|
10
|
-
assert.deepStrictEqual(inspect(parser('![]{javascript:alert}')), [['<img class="
|
|
11
|
-
assert.deepStrictEqual(inspect(parser('![]{vbscript:alert}')), [['<img class="
|
|
12
|
-
assert.deepStrictEqual(inspect(parser('![]{data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K}')), [['<img class="
|
|
13
|
-
assert.deepStrictEqual(inspect(parser('![]{any:alert}')), [['<img class="
|
|
10
|
+
assert.deepStrictEqual(inspect(parser('![]{javascript:alert}')), [['<img class="invalid" data-src="javascript:alert" alt="">'], '']);
|
|
11
|
+
assert.deepStrictEqual(inspect(parser('![]{vbscript:alert}')), [['<img class="invalid" data-src="vbscript:alert" alt="">'], '']);
|
|
12
|
+
assert.deepStrictEqual(inspect(parser('![]{data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K}')), [['<img class="invalid" data-src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K" alt="">'], '']);
|
|
13
|
+
assert.deepStrictEqual(inspect(parser('![]{any:alert}')), [['<img class="invalid" data-src="any:alert" alt="">'], '']);
|
|
14
14
|
assert.deepStrictEqual(inspect(parser('![]{"}')), [['<a href=""" target="_blank"><img class="media" data-src=""" alt=""></a>'], '']);
|
|
15
15
|
assert.deepStrictEqual(inspect(parser('![]{\\}')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt=""></a>'], '']);
|
|
16
16
|
assert.deepStrictEqual(inspect(parser('![\\"]{/}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="""></a>'], '']);
|
|
@@ -30,6 +30,8 @@ describe('Unit: parser/inline/media', () => {
|
|
|
30
30
|
assert.deepStrictEqual(inspect(parser('![]{ }')), undefined);
|
|
31
31
|
assert.deepStrictEqual(inspect(parser('![]]{/}')), undefined);
|
|
32
32
|
assert.deepStrictEqual(inspect(parser('![]{{}')), undefined);
|
|
33
|
+
assert.deepStrictEqual(inspect(parser('![]{}}')), undefined);
|
|
34
|
+
assert.deepStrictEqual(inspect(parser('![]{{}}')), undefined);
|
|
33
35
|
assert.deepStrictEqual(inspect(parser('![]{{b}}')), undefined);
|
|
34
36
|
assert.deepStrictEqual(inspect(parser('![]{b\nc}')), undefined);
|
|
35
37
|
assert.deepStrictEqual(inspect(parser('![]{a\\\nc}')), undefined);
|
|
@@ -42,20 +44,20 @@ describe('Unit: parser/inline/media', () => {
|
|
|
42
44
|
assert.deepStrictEqual(inspect(parser('![\\ ]{b}')), undefined);
|
|
43
45
|
assert.deepStrictEqual(inspect(parser('![\\\n]{b}')), undefined);
|
|
44
46
|
assert.deepStrictEqual(inspect(parser('![	]{b}')), undefined);
|
|
45
|
-
assert.deepStrictEqual(inspect(parser('![&a;]{b}')), [['<img class="
|
|
47
|
+
assert.deepStrictEqual(inspect(parser('![&a;]{b}')), [['<img class="invalid" data-src="b" alt="&a;">'], '']);
|
|
46
48
|
assert.deepStrictEqual(inspect(parser('![[]{b}')), undefined);
|
|
47
49
|
assert.deepStrictEqual(inspect(parser('![]]{b}')), undefined);
|
|
48
50
|
assert.deepStrictEqual(inspect(parser('![a]{}')), undefined);
|
|
49
51
|
assert.deepStrictEqual(inspect(parser('![a\nb]{b}')), undefined);
|
|
50
52
|
assert.deepStrictEqual(inspect(parser('![a\\\nb]{b}')), undefined);
|
|
51
|
-
assert.deepStrictEqual(inspect(parser('![]{ttp://host}')), [['<img class="
|
|
52
|
-
assert.deepStrictEqual(inspect(parser('![]{tel:1234567890}')), [['<img class="
|
|
53
|
-
//assert.deepStrictEqual(inspect(parser('![]{http://[::ffff:0:0%1]}')), [['<img class="
|
|
54
|
-
//assert.deepStrictEqual(inspect(parser('![]{http://[::ffff:0:0/96]}')), [['<img class="
|
|
55
|
-
assert.deepStrictEqual(inspect(parser('![]{.}')), [['<img class="
|
|
56
|
-
assert.deepStrictEqual(inspect(parser('![]{..}')), [['<img class="
|
|
57
|
-
assert.deepStrictEqual(inspect(parser('![]{../}')), [['<img class="
|
|
58
|
-
assert.deepStrictEqual(inspect(parser('![]{/../b}')), [['<img class="
|
|
53
|
+
assert.deepStrictEqual(inspect(parser('![]{ttp://host}')), [['<img class="invalid" data-src="ttp://host" alt="">'], '']);
|
|
54
|
+
assert.deepStrictEqual(inspect(parser('![]{tel:1234567890}')), [['<img class="invalid" data-src="tel:1234567890" alt="">'], '']);
|
|
55
|
+
//assert.deepStrictEqual(inspect(parser('![]{http://[::ffff:0:0%1]}')), [['<img class="invalid" alt="">'], '']);
|
|
56
|
+
//assert.deepStrictEqual(inspect(parser('![]{http://[::ffff:0:0/96]}')), [['<img class="invalid" alt="">'], '']);
|
|
57
|
+
assert.deepStrictEqual(inspect(parser('![]{.}')), [['<img class="invalid" data-src="." alt="">'], '']);
|
|
58
|
+
assert.deepStrictEqual(inspect(parser('![]{..}')), [['<img class="invalid" data-src=".." alt="">'], '']);
|
|
59
|
+
assert.deepStrictEqual(inspect(parser('![]{../}')), [['<img class="invalid" data-src="../" alt="">'], '']);
|
|
60
|
+
assert.deepStrictEqual(inspect(parser('![]{/../b}')), [['<img class="invalid" data-src="/../b" alt="">'], '']);
|
|
59
61
|
assert.deepStrictEqual(inspect(parser(' ![]{b}')), undefined);
|
|
60
62
|
assert.deepStrictEqual(inspect(parser('[]{/}')), undefined);
|
|
61
63
|
});
|
|
@@ -69,6 +71,8 @@ describe('Unit: parser/inline/media', () => {
|
|
|
69
71
|
assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
|
|
70
72
|
assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
|
|
71
73
|
assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
|
|
74
|
+
assert.deepStrictEqual(inspect(parser('![]{"}')), [['<a href=""" target="_blank"><img class="media" data-src=""" alt=""></a>'], '']);
|
|
75
|
+
assert.deepStrictEqual(inspect(parser('![]{"}"}')), [['<a href=""" target="_blank"><img class="media" data-src=""" alt=""></a>'], '"}']);
|
|
72
76
|
assert.deepStrictEqual(inspect(parser('![]{\\}')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt=""></a>'], '']);
|
|
73
77
|
assert.deepStrictEqual(inspect(parser('![]{\\ }')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt=""></a>'], '']);
|
|
74
78
|
assert.deepStrictEqual(inspect(parser('![]{\\b}')), [['<a href="\\b" target="_blank"><img class="media" data-src="\\b" alt=""></a>'], '']);
|
|
@@ -5,7 +5,7 @@ import { unsafelink, uri, option as linkoption, resolve } from './link';
|
|
|
5
5
|
import { attributes } from './html';
|
|
6
6
|
import { unsafehtmlentity } from './htmlentity';
|
|
7
7
|
import { txt, linebreak, str } from '../source';
|
|
8
|
-
import {
|
|
8
|
+
import { invalid } from '../util';
|
|
9
9
|
import { ReadonlyURL } from 'spica/url';
|
|
10
10
|
import { push } from 'spica/array';
|
|
11
11
|
import { html, define } from 'typed-dom/dom';
|
|
@@ -51,7 +51,7 @@ export const media: MediaParser = lazy(() => constraint(State.media, false, vali
|
|
|
51
51
|
|| (cache = context.caches?.media?.get(url.href)?.cloneNode(true))
|
|
52
52
|
|| html('img', { class: 'media', 'data-src': url.source, alt: text });
|
|
53
53
|
assert(!el.matches('.invalid'));
|
|
54
|
-
cache?.hasAttribute('alt') && cache
|
|
54
|
+
cache?.hasAttribute('alt') && cache.setAttribute('alt', text);
|
|
55
55
|
if (!sanitize(el, url, text)) return [[el], rest];
|
|
56
56
|
assert(!el.matches('.invalid'));
|
|
57
57
|
define(el, attributes('media', push([], el.classList), optspec, params));
|
|
@@ -98,19 +98,25 @@ function sanitize(target: HTMLElement, uri: ReadonlyURL, alt: string): boolean {
|
|
|
98
98
|
case 'https:':
|
|
99
99
|
assert(uri.host);
|
|
100
100
|
if (/\/\.\.?(?:\/|$)/.test('/' + uri.source.slice(0, uri.source.search(/[?#]|$/)))) {
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
define(target, {
|
|
102
|
+
class: 'invalid',
|
|
103
|
+
...invalid('media', 'argument',
|
|
104
|
+
'Dot-segments cannot be used in media paths; use subresource paths instead')
|
|
105
|
+
});
|
|
103
106
|
return false;
|
|
104
107
|
}
|
|
105
108
|
break;
|
|
106
109
|
default:
|
|
107
|
-
|
|
110
|
+
define(target, { class: 'invalid', ...invalid('media', 'argument', 'Invalid protocol') });
|
|
108
111
|
return false;
|
|
109
112
|
}
|
|
110
113
|
if (alt.includes(Command.Escape)) {
|
|
111
|
-
define(target, {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
define(target, {
|
|
115
|
+
class: 'invalid',
|
|
116
|
+
alt: target.getAttribute('alt')?.replace(CmdRegExp.Escape, ''),
|
|
117
|
+
...invalid('media', 'argument',
|
|
118
|
+
`Cannot use invalid HTML entitiy "${alt.match(/&[0-9A-Za-z]+;/)![0]}"`)
|
|
119
|
+
});
|
|
114
120
|
return false;
|
|
115
121
|
}
|
|
116
122
|
return true;
|
|
@@ -6,6 +6,7 @@ import { str } from '../source';
|
|
|
6
6
|
import { blank, trimBlankStart, trimBlankNodeEnd } from '../visibility';
|
|
7
7
|
import { html, defrag } from 'typed-dom/dom';
|
|
8
8
|
import { unshift } from 'spica/array';
|
|
9
|
+
import { invalid } from '../util';
|
|
9
10
|
|
|
10
11
|
export const reference: ReferenceParser = lazy(() => constraint(State.reference, false, surround(
|
|
11
12
|
'[[',
|
|
@@ -38,12 +39,7 @@ const abbr: ReferenceParser.AbbrParser = surround(
|
|
|
38
39
|
function attributes(ns: (string | HTMLElement)[]): Record<string, string | undefined> {
|
|
39
40
|
switch (ns[0]) {
|
|
40
41
|
case '':
|
|
41
|
-
return {
|
|
42
|
-
class: 'invalid',
|
|
43
|
-
'data-invalid-syntax': 'reference',
|
|
44
|
-
'data-invalid-type': 'syntax',
|
|
45
|
-
'data-invalid-message': 'Invalid abbreviation',
|
|
46
|
-
};
|
|
42
|
+
return { class: 'invalid', ...invalid('reference', 'syntax', 'Invalid abbreviation') };
|
|
47
43
|
case '\n':
|
|
48
44
|
const abbr = ns[1] as string;
|
|
49
45
|
ns[0] = ns[1] = '';
|
|
@@ -5,6 +5,7 @@ import { sequence, surround, dup, lazy, fmap } from '../../combinator';
|
|
|
5
5
|
import { unsafehtmlentity } from './htmlentity';
|
|
6
6
|
import { text as txt, str } from '../source';
|
|
7
7
|
import { isTightNodeStart } from '../visibility';
|
|
8
|
+
import { invalid } from '../util';
|
|
8
9
|
import { unshift, push } from 'spica/array';
|
|
9
10
|
import { html, defrag } from 'typed-dom/dom';
|
|
10
11
|
|
|
@@ -104,9 +105,7 @@ function attributes(texts: string[], rubies: string[]): Record<string, string> {
|
|
|
104
105
|
ss[i] = ss[i].replace(CmdRegExp.Escape, '');
|
|
105
106
|
attrs ??= {
|
|
106
107
|
class: 'invalid',
|
|
107
|
-
|
|
108
|
-
'data-invalid-type': ss === texts ? 'content' : 'argument',
|
|
109
|
-
'data-invalid-message': 'Invalid HTML entity',
|
|
108
|
+
...invalid('ruby', ss === texts ? 'content' : 'argument', 'Invalid HTML entity'),
|
|
110
109
|
};
|
|
111
110
|
}
|
|
112
111
|
}
|
package/src/parser/util.ts
CHANGED
|
@@ -75,6 +75,18 @@ export function repeat<T extends HTMLElement | string>(symbol: string, parser: P
|
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
export function invalid(
|
|
79
|
+
syntax: string,
|
|
80
|
+
type: string,
|
|
81
|
+
message: string,
|
|
82
|
+
): Record<string, string> {
|
|
83
|
+
return {
|
|
84
|
+
'data-invalid-syntax': syntax,
|
|
85
|
+
'data-invalid-type': type,
|
|
86
|
+
'data-invalid-message': message,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
export function markInvalid<T extends Element>(
|
|
79
91
|
el: T,
|
|
80
92
|
syntax: string,
|