securemark 0.223.0 → 0.224.0

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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.224.0
4
+
5
+ - Change media parser to disallow relative paths.
6
+
3
7
  ## 0.223.0
4
8
 
5
9
  - Refine some parsers to allow and trim leading whitespace.
@@ -1,4 +1,4 @@
1
- /*! securemark v0.223.0 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED */
1
+ /*! securemark v0.224.0 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED */
2
2
  require = function () {
3
3
  function r(e, n, t) {
4
4
  function o(i, f) {
@@ -6625,7 +6625,7 @@ require = function () {
6625
6625
  exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('((', '))', '\n', (0, combinator_1.fmap)((0, combinator_1.surround)('((', (0, combinator_1.guard)(context => {
6626
6626
  var _a, _b, _c;
6627
6627
  return (_c = (_b = (_a = context.syntax) === null || _a === void 0 ? void 0 : _a.inline) === null || _b === void 0 ? void 0 : _b.annotation) !== null && _c !== void 0 ? _c : true;
6628
- }, (0, util_1.startLoose)((0, util_1.visible)((0, combinator_1.context)({
6628
+ }, (0, util_1.startLoose)((0, combinator_1.context)({
6629
6629
  syntax: {
6630
6630
  inline: {
6631
6631
  annotation: false,
@@ -6633,7 +6633,7 @@ require = function () {
6633
6633
  }
6634
6634
  },
6635
6635
  state: global_1.undefined
6636
- }, (0, combinator_1.union)([(0, combinator_1.some)(inline_1.inline, ')', /^\\?\n/)]))))), '))'), ns => [(0, typed_dom_1.html)('sup', { class: 'annotation' }, (0, util_1.trimNode)((0, typed_dom_1.defrag)(ns)))]))));
6636
+ }, (0, combinator_1.union)([(0, combinator_1.some)(inline_1.inline, ')', /^\\?\n/)])), '))')), '))'), ns => [(0, typed_dom_1.html)('sup', { class: 'annotation' }, (0, util_1.trimNode)((0, typed_dom_1.defrag)(ns)))]))));
6637
6637
  },
6638
6638
  {
6639
6639
  '../../combinator': 30,
@@ -7485,7 +7485,7 @@ require = function () {
7485
7485
  [(0, typed_dom_1.html)(tag, attributes('html', [], attrspec[tag], as))],
7486
7486
  rest
7487
7487
  ]), ([, tag]) => tag)),
7488
- (0, combinator_1.match)(/^(?=<(sup|sub|small|bdo|bdi)(?=[^\S\n]|>))/, (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.validate)(`<${ tag }`, `</${ tag }>`, (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${ tag }`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)('>'), true), (0, util_1.startLoose)((0, util_1.visible)((0, combinator_1.context)((() => {
7488
+ (0, combinator_1.match)(/^(?=<(sup|sub|small|bdo|bdi)(?=[^\S\n]|>))/, (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.validate)(`<${ tag }`, `</${ tag }>`, (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${ tag }`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)('>'), true), (0, util_1.startLoose)((0, combinator_1.context)((() => {
7489
7489
  switch (tag) {
7490
7490
  case 'sup':
7491
7491
  case 'sub':
@@ -7507,11 +7507,11 @@ require = function () {
7507
7507
  default:
7508
7508
  return {};
7509
7509
  }
7510
- })(), (0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), `</${ tag }>`)))), (0, source_1.str)(`</${ tag }>`), false, ([as, bs, cs], rest, context) => [
7510
+ })(), (0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), `</${ tag }>`)), `</${ tag }>`), (0, source_1.str)(`</${ tag }>`), false, ([as, bs, cs], rest, context) => [
7511
7511
  [elem(tag, as, (0, util_1.trimEndBR)((0, typed_dom_1.defrag)(bs)), cs, context)],
7512
7512
  rest
7513
7513
  ])), ([, tag]) => tag)),
7514
- (0, combinator_1.match)(/^(?=<([a-z]+)(?=[^\S\n]|>))/, (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.validate)(`<${ tag }`, `</${ tag }>`, (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${ tag }`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)('>'), true), (0, util_1.startLoose)((0, util_1.visible)((0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), `</${ tag }>`))), (0, source_1.str)(`</${ tag }>`), false, ([as, bs, cs], rest) => [
7514
+ (0, combinator_1.match)(/^(?=<([a-z]+)(?=[^\S\n]|>))/, (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.validate)(`<${ tag }`, `</${ tag }>`, (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${ tag }`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)('>'), true), (0, util_1.startLoose)((0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), `</${ tag }>`), `</${ tag }>`), (0, source_1.str)(`</${ tag }>`), false, ([as, bs, cs], rest) => [
7515
7515
  [elem(tag, as, (0, util_1.trimEndBR)((0, typed_dom_1.defrag)(bs)), cs, {})],
7516
7516
  rest
7517
7517
  ], ([as, bs], rest) => as.length === 1 ? [
@@ -7529,14 +7529,14 @@ require = function () {
7529
7529
  case 'sub':
7530
7530
  switch (true) {
7531
7531
  case (_b = (_a = context.state) === null || _a === void 0 ? void 0 : _a.in) === null || _b === void 0 ? void 0 : _b.supsub:
7532
- return invalid('nest', `<${ tag }> HTML tag cannot be used in <sup>/<sub> HTML tags.`, as, bs, cs);
7532
+ return invalid('nest', `<${ tag }> HTML tag cannot be used in <sup> or <sub> HTML tag.`, as, bs, cs);
7533
7533
  }
7534
7534
  break;
7535
7535
  case 'small':
7536
7536
  switch (true) {
7537
7537
  case (_d = (_c = context.state) === null || _c === void 0 ? void 0 : _c.in) === null || _d === void 0 ? void 0 : _d.supsub:
7538
7538
  case (_f = (_e = context.state) === null || _e === void 0 ? void 0 : _e.in) === null || _f === void 0 ? void 0 : _f.small:
7539
- return invalid('nest', `<${ tag }> HTML tag cannot be used in <sup>/<sub>/<small> HTML tags.`, as, bs, cs);
7539
+ return invalid('nest', `<${ tag }> HTML tag cannot be used in <sup>, <sub>, or <small> HTML tag.`, as, bs, cs);
7540
7540
  }
7541
7541
  break;
7542
7542
  }
@@ -7672,7 +7672,7 @@ require = function () {
7672
7672
  (0, combinator_1.context)({ syntax: { inline: { link: false } } }, (0, combinator_1.dup)((0, combinator_1.union)([
7673
7673
  (0, combinator_1.surround)('[', inline_1.media, ']'),
7674
7674
  (0, combinator_1.surround)('[', inline_1.shortmedia, ']'),
7675
- (0, combinator_1.surround)('[', (0, util_1.startLoose)((0, util_1.visible)((0, combinator_1.context)({
7675
+ (0, combinator_1.surround)('[', (0, util_1.startLoose)((0, combinator_1.context)({
7676
7676
  syntax: {
7677
7677
  inline: {
7678
7678
  annotation: false,
@@ -7683,18 +7683,18 @@ require = function () {
7683
7683
  autolink: false
7684
7684
  }
7685
7685
  }
7686
- }, (0, combinator_1.some)(inline_1.inline, ']', /^\\?\n/)))), ']', true)
7686
+ }, (0, combinator_1.some)(inline_1.inline, ']', /^\\?\n/)), ']'), ']', true)
7687
7687
  ]))),
7688
7688
  (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([
7689
7689
  exports.uri,
7690
7690
  (0, combinator_1.some)(exports.option)
7691
7691
  ]), /^[^\S\n]?}/))
7692
7692
  ])))), ([params, content = []], rest, context) => {
7693
- var _a, _b;
7693
+ var _a, _b, _c, _d, _e;
7694
7694
  if ((0, parser_1.eval)((0, combinator_1.some)(autolink_1.autolink)((0, util_1.stringify)(content), context), []).some(node => typeof node === 'object'))
7695
7695
  return;
7696
7696
  const INSECURE_URI = params.shift();
7697
- const el = create(INSECURE_URI, (0, util_1.trimNode)((0, typed_dom_1.defrag)(content)), new url_1.ReadonlyURL(resolve(INSECURE_URI, context.host || global_1.location, context.url || global_1.location), ((_a = context.host) === null || _a === void 0 ? void 0 : _a.href) || global_1.location.href), ((_b = context.host) === null || _b === void 0 ? void 0 : _b.origin) || global_1.location.origin);
7697
+ const el = create(INSECURE_URI, (0, util_1.trimNode)((0, typed_dom_1.defrag)(content)), new url_1.ReadonlyURL(resolve(INSECURE_URI, (_a = context.host) !== null && _a !== void 0 ? _a : global_1.location, (_c = (_b = context.url) !== null && _b !== void 0 ? _b : context.host) !== null && _c !== void 0 ? _c : global_1.location), ((_d = context.host) === null || _d === void 0 ? void 0 : _d.href) || global_1.location.href), ((_e = context.host) === null || _e === void 0 ? void 0 : _e.origin) || global_1.location.origin);
7698
7698
  if (el.classList.contains('invalid'))
7699
7699
  return [
7700
7700
  [el],
@@ -7716,40 +7716,41 @@ require = function () {
7716
7716
  (0, combinator_1.fmap)((0, source_1.str)(/^[^\S\n]+[^\n{}]+/), opt => [` \\${ opt.slice(1) }`])
7717
7717
  ]);
7718
7718
  function resolve(uri, host, source) {
7719
- var _a;
7720
7719
  switch (true) {
7721
7720
  case uri.slice(0, 2) === '^/':
7722
- const file = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
7723
- return file.includes('.') ? `${ host.pathname.slice(0, -file.length) }${ uri.slice(2) }` : `${ fillTrailingSlash(host.pathname) }${ uri.slice(2) }`;
7721
+ const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
7722
+ return last.includes('.') && /^[0-9]*[a-z][0-9a-z]*$/i.test(last.slice(last.lastIndexOf('.') + 1)) ? `${ host.pathname.slice(0, -last.length) }${ uri.slice(2) }` : `${ host.pathname.replace(/\/?$/, '/') }${ uri.slice(2) }`;
7724
7723
  case host.origin === source.origin && host.pathname === source.pathname:
7725
7724
  case uri.slice(0, 2) === '//':
7726
7725
  return uri;
7727
7726
  default:
7728
7727
  const target = new url_1.ReadonlyURL(uri, source.href);
7729
- return target.origin === ((_a = uri.match(/^[A-Za-z][0-9A-Za-z.+-]*:\/\/[^/?#]*/)) === null || _a === void 0 ? void 0 : _a[0]) ? uri : target.origin === host.origin ? target.href.slice(target.origin.length) : target.href;
7728
+ return target.origin === host.origin ? target.href.slice(target.origin.length) : target.href;
7730
7729
  }
7731
7730
  }
7732
7731
  exports.resolve = resolve;
7733
- function fillTrailingSlash(pathname) {
7734
- return pathname[pathname.length - 1] === '/' ? pathname : pathname + '/';
7735
- }
7736
- function create(address, content, uri, origin) {
7732
+ function create(INSECURE_URI, content, uri, origin) {
7737
7733
  let type;
7738
7734
  let description;
7739
7735
  switch (uri.protocol) {
7740
7736
  case 'http:':
7741
7737
  case 'https:':
7738
+ if (INSECURE_URI.slice(0, 2) === '^/' && /(?:\/\.\.?)(?:\/|$)/.test(INSECURE_URI.slice(0, INSECURE_URI.search(/[?#]|$/)))) {
7739
+ type = 'argument';
7740
+ description = 'Subresource paths cannot contain dot-segments.';
7741
+ break;
7742
+ }
7742
7743
  return (0, typed_dom_1.html)('a', {
7743
7744
  href: uri.source,
7744
7745
  target: undefined || uri.origin !== origin || typeof content[0] === 'object' && content[0].classList.contains('media') ? '_blank' : undefined
7745
- }, content.length === 0 ? decode(address) : content);
7746
+ }, content.length === 0 ? decode(INSECURE_URI) : content);
7746
7747
  case 'tel:':
7747
7748
  if (content.length === 0) {
7748
- content = [address];
7749
+ content = [INSECURE_URI];
7749
7750
  }
7750
7751
  const pattern = /^(?:tel:)?(?:\+(?!0))?\d+(?:-\d+)*$/i;
7751
7752
  switch (true) {
7752
- case content.length === 1 && typeof content[0] === 'string' && pattern.test(address) && pattern.test(content[0]) && address.replace(/[^+\d]/g, '') === content[0].replace(/[^+\d]/g, ''):
7753
+ case content.length === 1 && typeof content[0] === 'string' && pattern.test(INSECURE_URI) && pattern.test(content[0]) && INSECURE_URI.replace(/[^+\d]/g, '') === content[0].replace(/[^+\d]/g, ''):
7753
7754
  return (0, typed_dom_1.html)('a', { href: uri.source }, content);
7754
7755
  }
7755
7756
  type = 'content';
@@ -7761,7 +7762,7 @@ require = function () {
7761
7762
  'data-invalid-syntax': 'link',
7762
7763
  'data-invalid-type': type !== null && type !== void 0 ? type : type = 'argument',
7763
7764
  'data-invalid-description': description !== null && description !== void 0 ? description : description = 'Invalid protocol.'
7764
- }, content.length === 0 ? address : content);
7765
+ }, content.length === 0 ? INSECURE_URI : content);
7765
7766
  }
7766
7767
  function decode(uri) {
7767
7768
  if (uri.indexOf('%') === -1)
@@ -7907,24 +7908,23 @@ require = function () {
7907
7908
  [''],
7908
7909
  as
7909
7910
  ]), ([[text]]) => text === '' || text.trim() !== ''), ([[text], params], rest, context) => {
7910
- var _a, _b, _c, _d;
7911
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
7911
7912
  const INSECURE_URI = params.shift();
7912
- const url = new url_1.ReadonlyURL((0, link_1.resolve)(INSECURE_URI, context.host || global_1.location, context.url || global_1.location), ((_a = context.host) === null || _a === void 0 ? void 0 : _a.href) || global_1.location.href);
7913
- const cache = (_b = context.caches) === null || _b === void 0 ? void 0 : _b.media;
7914
- const cached = cache === null || cache === void 0 ? void 0 : cache.has(url.href);
7915
- const el = cache && cached ? cache.get(url.href).cloneNode(true) : (0, typed_dom_1.html)('img', {
7913
+ const url = new url_1.ReadonlyURL((0, link_1.resolve)(INSECURE_URI, (_a = context.host) !== null && _a !== void 0 ? _a : global_1.location, (_c = (_b = context.url) !== null && _b !== void 0 ? _b : context.host) !== null && _c !== void 0 ? _c : global_1.location), ((_d = context.host) === null || _d === void 0 ? void 0 : _d.href) || global_1.location.href);
7914
+ let cache;
7915
+ const el = global_1.undefined || (cache = (_g = (_f = (_e = context.caches) === null || _e === void 0 ? void 0 : _e.media) === null || _f === void 0 ? void 0 : _f.get(url.href)) === null || _g === void 0 ? void 0 : _g.cloneNode(true)) || (0, typed_dom_1.html)('img', {
7916
7916
  class: 'media',
7917
7917
  'data-src': url.source,
7918
7918
  alt: text
7919
7919
  });
7920
- if (!cached && !sanitize(url, el))
7920
+ if (!cache && !sanitize(url, el))
7921
7921
  return [
7922
7922
  [el],
7923
7923
  rest
7924
7924
  ];
7925
- cached && el.hasAttribute('alt') && el.setAttribute('alt', text);
7925
+ (cache === null || cache === void 0 ? void 0 : cache.hasAttribute('alt')) && (cache === null || cache === void 0 ? void 0 : cache.setAttribute('alt', text));
7926
7926
  (0, typed_dom_1.define)(el, (0, html_1.attributes)('media', (0, array_1.push)([], el.classList), optspec, params));
7927
- if (((_d = (_c = context.syntax) === null || _c === void 0 ? void 0 : _c.inline) === null || _d === void 0 ? void 0 : _d.link) === false || cached && el.tagName !== 'IMG')
7927
+ if (((_j = (_h = context.syntax) === null || _h === void 0 ? void 0 : _h.inline) === null || _j === void 0 ? void 0 : _j.link) === false || cache && cache.tagName !== 'IMG')
7928
7928
  return [
7929
7929
  [el],
7930
7930
  rest
@@ -7970,18 +7970,29 @@ require = function () {
7970
7970
  link_1.option
7971
7971
  ]);
7972
7972
  function sanitize(uri, target) {
7973
+ if (/^\.\.?\//.test(uri.source)) {
7974
+ (0, typed_dom_1.define)(target, {
7975
+ class: void target.classList.add('invalid'),
7976
+ 'data-invalid-syntax': 'media',
7977
+ 'data-invalid-type': 'argument',
7978
+ 'data-invalid-description': 'Relative paths cannot be used with media syntax; Use subresource paths instead.'
7979
+ });
7980
+ return false;
7981
+ }
7973
7982
  switch (uri.protocol) {
7974
7983
  case 'http:':
7975
7984
  case 'https:':
7976
- return true;
7985
+ break;
7986
+ default:
7987
+ (0, typed_dom_1.define)(target, {
7988
+ class: void target.classList.add('invalid'),
7989
+ 'data-invalid-syntax': 'media',
7990
+ 'data-invalid-type': 'argument',
7991
+ 'data-invalid-description': 'Invalid protocol.'
7992
+ });
7993
+ return false;
7977
7994
  }
7978
- (0, typed_dom_1.define)(target, {
7979
- class: void target.classList.add('invalid'),
7980
- 'data-invalid-syntax': 'media',
7981
- 'data-invalid-type': 'argument',
7982
- 'data-invalid-description': 'Invalid protocol.'
7983
- });
7984
- return false;
7995
+ return true;
7985
7996
  }
7986
7997
  },
7987
7998
  {
@@ -8011,7 +8022,7 @@ require = function () {
8011
8022
  exports.reference = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('[[', ']]', '\n', (0, combinator_1.fmap)((0, combinator_1.surround)('[[', (0, combinator_1.guard)(context => {
8012
8023
  var _a, _b, _c;
8013
8024
  return (_c = (_b = (_a = context.syntax) === null || _a === void 0 ? void 0 : _a.inline) === null || _b === void 0 ? void 0 : _b.reference) !== null && _c !== void 0 ? _c : true;
8014
- }, (0, util_1.startLoose)((0, util_1.visible)((0, combinator_1.context)({
8025
+ }, (0, util_1.startLoose)((0, combinator_1.context)({
8015
8026
  syntax: {
8016
8027
  inline: {
8017
8028
  annotation: false,
@@ -8030,8 +8041,8 @@ require = function () {
8030
8041
  ''
8031
8042
  ]),
8032
8043
  (0, combinator_1.some)(inline_1.inline, ']', /^\\?\n/)
8033
- ]))))), ']]'), ns => [(0, typed_dom_1.html)('sup', attributes(ns), (0, util_1.trimNode)((0, typed_dom_1.defrag)(ns)))]))));
8034
- const abbr = (0, combinator_1.creator)((0, combinator_1.fmap)((0, combinator_1.verify)((0, combinator_1.surround)('^', (0, combinator_1.union)([(0, source_1.str)(/^(?![0-9]+\s?[|\]])[0-9A-Za-z]+(?:(?:-|(?=\W)(?!'\d)'?(?!\.\d)\.?(?!,\S),? ?)[0-9A-Za-z]+)*(?:-|'?\.?,? ?)?/)]), /^\|?(?=]])|^\|[^\S\n]+/), (_, rest, context) => (0, util_1.isStartTight)(rest, context)), ([source]) => [(0, typed_dom_1.html)('abbr', source)]));
8044
+ ])), ']]')), ']]'), ns => [(0, typed_dom_1.html)('sup', attributes(ns), (0, util_1.trimNode)((0, typed_dom_1.defrag)(ns)))]))));
8045
+ const abbr = (0, combinator_1.creator)((0, combinator_1.fmap)((0, combinator_1.verify)((0, combinator_1.surround)('^', (0, combinator_1.union)([(0, source_1.str)(/^(?![0-9]+\s?[|\]])[0-9A-Za-z]+(?:(?:-|(?=\W)(?!'\d)'?(?!\.\d)\.?(?!,\S),? ?)[0-9A-Za-z]+)*(?:-|'?\.?,? ?)?/)]), /^\|?(?=]])|^\|[^\S\n]/), (_, rest, context) => (0, util_1.isStartLoose)(rest, context)), ([source]) => [(0, typed_dom_1.html)('abbr', source)]));
8035
8046
  function attributes(ns) {
8036
8047
  return typeof ns[0] === 'object' && ns[0].tagName === 'ABBR' ? {
8037
8048
  class: 'reference',
@@ -8942,7 +8953,7 @@ require = function () {
8942
8953
  function (_dereq_, module, exports) {
8943
8954
  'use strict';
8944
8955
  Object.defineProperty(exports, '__esModule', { value: true });
8945
- exports.stringify = exports.trimEndBR = exports.trimNodeEnd = exports.trimNode = exports.visible = exports.isEndTightNodes = exports.isStartTightNodes = exports.isStartTight = exports.startTight = exports.startLoose = exports.visualize = void 0;
8956
+ exports.stringify = exports.trimEndBR = exports.trimNodeEnd = exports.trimNode = exports.isEndTightNodes = exports.isStartTightNodes = exports.startTight = exports.isStartLoose = exports.startLoose = exports.visualize = void 0;
8946
8957
  const global_1 = _dereq_('spica/global');
8947
8958
  const parser_1 = _dereq_('../combinator/data/parser');
8948
8959
  const combinator_1 = _dereq_('../combinator');
@@ -9017,15 +9028,18 @@ require = function () {
9017
9028
  }
9018
9029
  return false;
9019
9030
  }
9020
- function startLoose(parser) {
9021
- return (source, context) => isStartLoose(source, context) ? parser(source, context) : global_1.undefined;
9031
+ function startLoose(parser, except) {
9032
+ return (source, context) => isStartLoose(source, context, except) ? parser(source, context) : global_1.undefined;
9022
9033
  }
9023
9034
  exports.startLoose = startLoose;
9024
- function isStartLoose(source, context) {
9035
+ function isStartLoose(source, context, except) {
9036
+ var _a;
9037
+ source && (source = source.replace(/^[^\S\n]+/, ''));
9025
9038
  if (source === '')
9026
9039
  return true;
9027
- return isStartTight(source.replace(/^[^\S\n]+/, ''), context);
9040
+ return source.slice(0, (_a = except === null || except === void 0 ? void 0 : except.length) !== null && _a !== void 0 ? _a : 0) !== except && isStartTight(source, context);
9028
9041
  }
9042
+ exports.isStartLoose = isStartLoose;
9029
9043
  function startTight(parser) {
9030
9044
  return (source, context) => isStartTight(source, context) ? parser(source, context) : global_1.undefined;
9031
9045
  }
@@ -9064,7 +9078,6 @@ require = function () {
9064
9078
  return source[0].trimStart() !== '';
9065
9079
  }
9066
9080
  }
9067
- exports.isStartTight = isStartTight;
9068
9081
  function isStartTightNodes(nodes) {
9069
9082
  if (nodes.length === 0)
9070
9083
  return true;
@@ -9078,18 +9091,6 @@ require = function () {
9078
9091
  return typeof nodes[last] === 'string' && nodes[last].length > 1 ? isVisible(nodes[last], -1) || isVisible(nodes[last], -2) : isVisible(nodes[last], -1) || last === 0 || isVisible(nodes[last - 1], -1);
9079
9092
  }
9080
9093
  exports.isEndTightNodes = isEndTightNodes;
9081
- function visible(parser) {
9082
- return (0, combinator_1.verify)(parser, nodes => {
9083
- if (nodes.length === 0)
9084
- return true;
9085
- for (let i = 0; i < nodes.length; ++i) {
9086
- if (isVisible(nodes[i]))
9087
- return true;
9088
- }
9089
- return false;
9090
- });
9091
- }
9092
- exports.visible = visible;
9093
9094
  function isVisible(node, position) {
9094
9095
  if (!node)
9095
9096
  return false;
@@ -9216,8 +9217,6 @@ require = function () {
9216
9217
  }));
9217
9218
  function render(source, opts = {}) {
9218
9219
  opts = extend(opts);
9219
- if (source.classList.contains('invalid'))
9220
- return;
9221
9220
  const base = global_1.location.href;
9222
9221
  if (source.matches(selector))
9223
9222
  return void render_(base, source, opts);
@@ -9228,6 +9227,8 @@ require = function () {
9228
9227
  exports.render = render;
9229
9228
  function render_(base, source, opts) {
9230
9229
  var _a, _b, _c;
9230
+ if (source.classList.contains('invalid'))
9231
+ return;
9231
9232
  try {
9232
9233
  switch (true) {
9233
9234
  case !!opts.code && !source.firstElementChild && source.matches('pre.code'):
package/package-lock.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.223.0",
3
+ "version": "0.224.0",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
@@ -399,9 +399,9 @@
399
399
  }
400
400
  },
401
401
  "@npmcli/fs": {
402
- "version": "1.0.0",
403
- "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.0.0.tgz",
404
- "integrity": "sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ==",
402
+ "version": "1.1.0",
403
+ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.0.tgz",
404
+ "integrity": "sha512-VhP1qZLXcrXRIaPoqb4YA55JQxLNF3jNR4T55IdOJa3+IFJKNYHtPvtXx8slmeMavj37vCzCfrqQM1vWLsYKLA==",
405
405
  "dev": true,
406
406
  "requires": {
407
407
  "@gar/promisify": "^1.0.1",
@@ -1338,21 +1338,21 @@
1338
1338
  "dev": true
1339
1339
  },
1340
1340
  "body-parser": {
1341
- "version": "1.19.0",
1342
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
1343
- "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
1341
+ "version": "1.19.1",
1342
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz",
1343
+ "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==",
1344
1344
  "dev": true,
1345
1345
  "requires": {
1346
- "bytes": "3.1.0",
1346
+ "bytes": "3.1.1",
1347
1347
  "content-type": "~1.0.4",
1348
1348
  "debug": "2.6.9",
1349
1349
  "depd": "~1.1.2",
1350
- "http-errors": "1.7.2",
1350
+ "http-errors": "1.8.1",
1351
1351
  "iconv-lite": "0.4.24",
1352
1352
  "on-finished": "~2.3.0",
1353
- "qs": "6.7.0",
1354
- "raw-body": "2.4.0",
1355
- "type-is": "~1.6.17"
1353
+ "qs": "6.9.6",
1354
+ "raw-body": "2.4.2",
1355
+ "type-is": "~1.6.18"
1356
1356
  }
1357
1357
  },
1358
1358
  "boxen": {
@@ -1672,9 +1672,9 @@
1672
1672
  "dev": true
1673
1673
  },
1674
1674
  "bytes": {
1675
- "version": "3.1.0",
1676
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
1677
- "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
1675
+ "version": "3.1.1",
1676
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz",
1677
+ "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==",
1678
1678
  "dev": true
1679
1679
  },
1680
1680
  "cacache": {
@@ -2887,9 +2887,9 @@
2887
2887
  "dev": true
2888
2888
  },
2889
2889
  "electron-to-chromium": {
2890
- "version": "1.4.14",
2891
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.14.tgz",
2892
- "integrity": "sha512-RsGkAN9JEAYMObS72kzUsPPcPGMqX1rBqGuXi9aa4TBKLzICoLf+DAAtd0fVFzrniJqYzpby47gthCUoObfs0Q==",
2890
+ "version": "1.4.16",
2891
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.16.tgz",
2892
+ "integrity": "sha512-BQb7FgYwnu6haWLU63/CdVW+9xhmHls3RCQUFiV4lvw3wimEHTVcUk2hkuZo76QhR8nnDdfZE7evJIZqijwPdA==",
2893
2893
  "dev": true
2894
2894
  },
2895
2895
  "elliptic": {
@@ -5153,24 +5153,16 @@
5153
5153
  "dev": true
5154
5154
  },
5155
5155
  "http-errors": {
5156
- "version": "1.7.2",
5157
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
5158
- "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
5156
+ "version": "1.8.1",
5157
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
5158
+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
5159
5159
  "dev": true,
5160
5160
  "requires": {
5161
5161
  "depd": "~1.1.2",
5162
- "inherits": "2.0.3",
5163
- "setprototypeof": "1.1.1",
5162
+ "inherits": "2.0.4",
5163
+ "setprototypeof": "1.2.0",
5164
5164
  "statuses": ">= 1.5.0 < 2",
5165
- "toidentifier": "1.0.0"
5166
- },
5167
- "dependencies": {
5168
- "inherits": {
5169
- "version": "2.0.3",
5170
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
5171
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
5172
- "dev": true
5173
- }
5165
+ "toidentifier": "1.0.1"
5174
5166
  }
5175
5167
  },
5176
5168
  "http-proxy": {
@@ -5621,9 +5613,9 @@
5621
5613
  "dev": true
5622
5614
  },
5623
5615
  "is-negative-zero": {
5624
- "version": "2.0.1",
5625
- "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
5626
- "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==",
5616
+ "version": "2.0.2",
5617
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
5618
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
5627
5619
  "dev": true
5628
5620
  },
5629
5621
  "is-npm": {
@@ -5782,12 +5774,12 @@
5782
5774
  "dev": true
5783
5775
  },
5784
5776
  "is-weakref": {
5785
- "version": "1.0.1",
5786
- "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz",
5787
- "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==",
5777
+ "version": "1.0.2",
5778
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
5779
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
5788
5780
  "dev": true,
5789
5781
  "requires": {
5790
- "call-bind": "^1.0.0"
5782
+ "call-bind": "^1.0.2"
5791
5783
  }
5792
5784
  },
5793
5785
  "is-windows": {
@@ -6854,9 +6846,9 @@
6854
6846
  "dev": true
6855
6847
  },
6856
6848
  "minipass": {
6857
- "version": "3.1.5",
6858
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz",
6859
- "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==",
6849
+ "version": "3.1.6",
6850
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz",
6851
+ "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==",
6860
6852
  "dev": true,
6861
6853
  "requires": {
6862
6854
  "yallist": "^4.0.0"
@@ -8367,9 +8359,9 @@
8367
8359
  "dev": true
8368
8360
  },
8369
8361
  "qs": {
8370
- "version": "6.7.0",
8371
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
8372
- "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
8362
+ "version": "6.9.6",
8363
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz",
8364
+ "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==",
8373
8365
  "dev": true
8374
8366
  },
8375
8367
  "querystring": {
@@ -8416,13 +8408,13 @@
8416
8408
  "dev": true
8417
8409
  },
8418
8410
  "raw-body": {
8419
- "version": "2.4.0",
8420
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
8421
- "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
8411
+ "version": "2.4.2",
8412
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz",
8413
+ "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==",
8422
8414
  "dev": true,
8423
8415
  "requires": {
8424
- "bytes": "3.1.0",
8425
- "http-errors": "1.7.2",
8416
+ "bytes": "3.1.1",
8417
+ "http-errors": "1.8.1",
8426
8418
  "iconv-lite": "0.4.24",
8427
8419
  "unpipe": "1.0.0"
8428
8420
  }
@@ -9067,9 +9059,9 @@
9067
9059
  }
9068
9060
  },
9069
9061
  "setprototypeof": {
9070
- "version": "1.1.1",
9071
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
9072
- "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
9062
+ "version": "1.2.0",
9063
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
9064
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
9073
9065
  "dev": true
9074
9066
  },
9075
9067
  "sha.js": {
@@ -9888,9 +9880,9 @@
9888
9880
  }
9889
9881
  },
9890
9882
  "toidentifier": {
9891
- "version": "1.0.0",
9892
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
9893
- "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
9883
+ "version": "1.0.1",
9884
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
9885
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
9894
9886
  "dev": true
9895
9887
  },
9896
9888
  "transformify": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.223.0",
3
+ "version": "0.224.0",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -104,10 +104,6 @@ describe('Unit: parser/api/parse', () => {
104
104
  '{a}',
105
105
  '{/a}',
106
106
  '{^/a}',
107
- '{^/a/..}',
108
- '{^/a/../}',
109
- '{^/a?/../}',
110
- '{^/a#/../}',
111
107
  '{./a}',
112
108
  '{../a}',
113
109
  '{../../a}',
@@ -131,10 +127,6 @@ describe('Unit: parser/api/parse', () => {
131
127
  '<p><a href="https://source/x/a" target="_blank">a</a></p>',
132
128
  '<p><a href="https://source/a" target="_blank">/a</a></p>',
133
129
  '<p><a href="/z/a">^/a</a></p>',
134
- '<p><a href="/z/a/..">^/a/..</a></p>',
135
- '<p><a href="/z/a/../">^/a/../</a></p>',
136
- '<p><a href="/z/a?/../">^/a?/../</a></p>',
137
- '<p><a href="/z/a#/../">^/a#/../</a></p>',
138
130
  '<p><a href="https://source/x/a" target="_blank">./a</a></p>',
139
131
  '<p><a href="https://source/a" target="_blank">../a</a></p>',
140
132
  '<p><a href="https://source/a" target="_blank">../../a</a></p>',
@@ -2,13 +2,13 @@ import { undefined } from 'spica/global';
2
2
  import { AnnotationParser } from '../inline';
3
3
  import { union, some, validate, guard, context, creator, surround, lazy, fmap } from '../../combinator';
4
4
  import { inline } from '../inline';
5
- import { startLoose, visible, trimNode } from '../util';
5
+ import { startLoose, trimNode } from '../util';
6
6
  import { html, defrag } from 'typed-dom';
7
7
 
8
8
  export const annotation: AnnotationParser = lazy(() => creator(validate('((', '))', '\n', fmap(surround(
9
9
  '((',
10
10
  guard(context => context.syntax?.inline?.annotation ?? true,
11
- startLoose(visible(
11
+ startLoose(
12
12
  context({ syntax: { inline: {
13
13
  annotation: false,
14
14
  // Redundant
@@ -20,6 +20,6 @@ export const annotation: AnnotationParser = lazy(() => creator(validate('((', ')
20
20
  //link: true,
21
21
  //autolink: true,
22
22
  }}, state: undefined },
23
- union([some(inline, ')', /^\\?\n/)]))))),
23
+ union([some(inline, ')', /^\\?\n/)])), '))')),
24
24
  '))'),
25
25
  ns => [html('sup', { class: 'annotation' }, trimNode(defrag(ns)))]))));
@@ -5,7 +5,7 @@ import { HTMLParser } from '../inline';
5
5
  import { union, some, validate, context, creator, surround, match, lazy } from '../../combinator';
6
6
  import { inline } from '../inline';
7
7
  import { str } from '../source';
8
- import { startLoose, visible, trimEndBR } from '../util';
8
+ import { startLoose, trimEndBR } from '../util';
9
9
  import { html as h, defrag } from 'typed-dom';
10
10
  import { memoize } from 'spica/memoize';
11
11
  import { Cache } from 'spica/cache';
@@ -39,7 +39,7 @@ export const html: HTMLParser = lazy(() => creator(validate('<', '>', '\n', vali
39
39
  validate(`<${tag}`, `</${tag}>`,
40
40
  surround<HTMLParser.TagParser, string>(surround(
41
41
  str(`<${tag}`), some(attribute), str('>'), true),
42
- startLoose(visible(
42
+ startLoose(
43
43
  context((() => {
44
44
  switch (tag) {
45
45
  case 'sup':
@@ -63,7 +63,7 @@ export const html: HTMLParser = lazy(() => creator(validate('<', '>', '\n', vali
63
63
  return {};
64
64
  }
65
65
  })(),
66
- some(union([inline]), `</${tag}>`)))),
66
+ some(union([inline]), `</${tag}>`)), `</${tag}>`),
67
67
  str(`</${tag}>`), false,
68
68
  ([as, bs, cs], rest, context) =>
69
69
  [[elem(tag, as, trimEndBR(defrag(bs)), cs, context)], rest])),
@@ -75,7 +75,7 @@ export const html: HTMLParser = lazy(() => creator(validate('<', '>', '\n', vali
75
75
  validate(`<${tag}`, `</${tag}>`,
76
76
  surround<HTMLParser.TagParser, string>(surround(
77
77
  str(`<${tag}`), some(attribute), str('>'), true),
78
- startLoose(visible(some(union([inline]), `</${tag}>`))),
78
+ startLoose(some(union([inline]), `</${tag}>`), `</${tag}>`),
79
79
  str(`</${tag}>`), false,
80
80
  ([as, bs, cs], rest) =>
81
81
  [[elem(tag, as, trimEndBR(defrag(bs)), cs, {})], rest],
@@ -99,14 +99,14 @@ function elem(tag: string, as: (HTMLElement | string)[], bs: (HTMLElement | stri
99
99
  case 'sub':
100
100
  switch (true) {
101
101
  case context.state?.in?.supsub:
102
- return invalid('nest', `<${tag}> HTML tag cannot be used in <sup>/<sub> HTML tags.`, as, bs, cs);
102
+ return invalid('nest', `<${tag}> HTML tag cannot be used in <sup> or <sub> HTML tag.`, as, bs, cs);
103
103
  }
104
104
  break;
105
105
  case 'small':
106
106
  switch (true) {
107
107
  case context.state?.in?.supsub:
108
108
  case context.state?.in?.small:
109
- return invalid('nest', `<${tag}> HTML tag cannot be used in <sup>/<sub>/<small> HTML tags.`, as, bs, cs);
109
+ return invalid('nest', `<${tag}> HTML tag cannot be used in <sup>, <sub>, or <small> HTML tag.`, as, bs, cs);
110
110
  }
111
111
  break;
112
112
  }
@@ -1,10 +1,11 @@
1
1
  import { link } from './link';
2
2
  import { some } from '../../combinator';
3
3
  import { inspect } from '../../debug.test';
4
+ import { MarkdownParser } from '../../../markdown';
4
5
 
5
6
  describe('Unit: parser/inline/link', () => {
6
7
  describe('link', () => {
7
- const parser = (source: string) => some(link)(source, {});
8
+ const parser = (source: string, context: MarkdownParser.Context = {}) => some(link)(source, context);
8
9
 
9
10
  it('xss', () => {
10
11
  assert.deepStrictEqual(inspect(parser('[]{javascript:alert}')), [['<a class="invalid">javascript:alert</a>'], '']);
@@ -89,10 +90,28 @@ describe('Unit: parser/inline/link', () => {
89
90
  assert.deepStrictEqual(inspect(parser('[]{\\b}')), [[`<a href="\\b">\\b</a>`], '']);
90
91
  assert.deepStrictEqual(inspect(parser('[]{?b=c+d&\\#}')), [['<a href="?b=c+d&amp;\\#">?b=c+d&amp;\\#</a>'], '']);
91
92
  assert.deepStrictEqual(inspect(parser('[]{?&amp;}')), [['<a href="?&amp;amp;">?&amp;amp;</a>'], '']);
92
- assert.deepStrictEqual(inspect(parser('[]{./b}')), [['<a href="./b">./b</a>'], '']);
93
- assert.deepStrictEqual(inspect(parser('[]{^/b}')), [[`<a href="/b">^/b</a>`], '']);
94
93
  assert.deepStrictEqual(inspect(parser('[]{#}')), [['<a href="#">#</a>'], '']);
95
94
  assert.deepStrictEqual(inspect(parser('[]{#b}')), [['<a href="#b">#b</a>'], '']);
95
+ assert.deepStrictEqual(inspect(parser('[]{./b}')), [['<a href="./b">./b</a>'], '']);
96
+ assert.deepStrictEqual(inspect(parser('[]{^/b}')), [[`<a href="/b">^/b</a>`], '']);
97
+ assert.deepStrictEqual(inspect(parser('[]{^/b/.}')), [[`<a class="invalid">^/b/.</a>`], '']);
98
+ assert.deepStrictEqual(inspect(parser('[]{^/b/./}')), [[`<a class="invalid">^/b/./</a>`], '']);
99
+ assert.deepStrictEqual(inspect(parser('[]{^/b/..}')), [[`<a class="invalid">^/b/..</a>`], '']);
100
+ assert.deepStrictEqual(inspect(parser('[]{^/b/../}')), [[`<a class="invalid">^/b/../</a>`], '']);
101
+ assert.deepStrictEqual(inspect(parser('[]{^/b/../..}')), [[`<a class="invalid">^/b/../..</a>`], '']);
102
+ assert.deepStrictEqual(inspect(parser('[]{^/b/../c}')), [[`<a class="invalid">^/b/../c</a>`], '']);
103
+ assert.deepStrictEqual(inspect(parser('[]{^/b/../c/..}')), [[`<a class="invalid">^/b/../c/..</a>`], '']);
104
+ assert.deepStrictEqual(inspect(parser('[]{^/b?/../}')), [[`<a href="/b?/../">^/b?/../</a>`], '']);
105
+ assert.deepStrictEqual(inspect(parser('[]{^/b#/../}')), [[`<a href="/b#/../">^/b#/../</a>`], '']);
106
+ assert.deepStrictEqual(inspect(parser('[]{^/b}', { host: new URL('/dir', location.origin) })), [[`<a href="/dir/b">^/b</a>`], '']);
107
+ assert.deepStrictEqual(inspect(parser('[]{^/b}', { host: new URL('/dir/', location.origin) })), [[`<a href="/dir/b">^/b</a>`], '']);
108
+ assert.deepStrictEqual(inspect(parser('[]{^/b}', { host: new URL('/folder/doc.md', location.origin) })), [[`<a href="/folder/b">^/b</a>`], '']);
109
+ assert.deepStrictEqual(inspect(parser('[]{^/b}', { host: new URL('/folder/doc.md/', location.origin) })), [[`<a href="/folder/doc.md/b">^/b</a>`], '']);
110
+ assert.deepStrictEqual(inspect(parser('[]{^/b}', { host: new URL('/.file', location.origin) })), [[`<a href="/b">^/b</a>`], '']);
111
+ assert.deepStrictEqual(inspect(parser('[]{^/b}', { host: new URL('/0.0a', location.origin) })), [[`<a href="/b">^/b</a>`], '']);
112
+ assert.deepStrictEqual(inspect(parser('[]{^/b}', { host: new URL('/0.a0', location.origin) })), [[`<a href="/b">^/b</a>`], '']);
113
+ assert.deepStrictEqual(inspect(parser('[]{^/b}', { host: new URL('/0.0', location.origin) })), [[`<a href="/0.0/b">^/b</a>`], '']);
114
+ assert.deepStrictEqual(inspect(parser('[]{^/b}', { host: new URL('/0.0,0.0,0z', location.origin) })), [[`<a href="/0.0,0.0,0z/b">^/b</a>`], '']);
96
115
  assert.deepStrictEqual(inspect(parser('[ a]{b}')), [['<a href="b">a</a>'], '']);
97
116
  assert.deepStrictEqual(inspect(parser('[ a ]{b}')), [['<a href="b">a</a>'], '']);
98
117
  assert.deepStrictEqual(inspect(parser('[a ]{b}')), [['<a href="b">a</a>'], '']);
@@ -7,7 +7,7 @@ import { inline, media, shortmedia } from '../inline';
7
7
  import { attributes } from './html';
8
8
  import { autolink } from '../autolink';
9
9
  import { str } from '../source';
10
- import { startLoose, visible, trimNode, stringify } from '../util';
10
+ import { startLoose, trimNode, stringify } from '../util';
11
11
  import { html, define, defrag } from 'typed-dom';
12
12
  import { ReadonlyURL } from 'spica/url';
13
13
 
@@ -28,7 +28,7 @@ export const link: LinkParser = lazy(() => creator(10, bind(reverse(
28
28
  surround('[', shortmedia, ']'),
29
29
  surround(
30
30
  '[',
31
- startLoose(visible(
31
+ startLoose(
32
32
  context({ syntax: { inline: {
33
33
  annotation: false,
34
34
  reference: false,
@@ -39,7 +39,7 @@ export const link: LinkParser = lazy(() => creator(10, bind(reverse(
39
39
  media: false,
40
40
  autolink: false,
41
41
  }}},
42
- some(inline, ']', /^\\?\n/)))),
42
+ some(inline, ']', /^\\?\n/)), ']'),
43
43
  ']',
44
44
  true),
45
45
  ]))),
@@ -56,7 +56,7 @@ export const link: LinkParser = lazy(() => creator(10, bind(reverse(
56
56
  INSECURE_URI,
57
57
  trimNode(defrag(content)),
58
58
  new ReadonlyURL(
59
- resolve(INSECURE_URI, context.host || location, context.url || location),
59
+ resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
60
60
  context.host?.href || location.href),
61
61
  context.host?.origin || location.origin);
62
62
  if (el.classList.contains('invalid')) return [[el], rest];
@@ -81,33 +81,25 @@ export function resolve(uri: string, host: URL | Location, source: URL | Locatio
81
81
  assert(uri === uri.trim());
82
82
  switch (true) {
83
83
  case uri.slice(0, 2) === '^/':
84
- const file = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
85
- return file.includes('.')
86
- ? `${host.pathname.slice(0, -file.length)}${uri.slice(2)}`
87
- : `${fillTrailingSlash(host.pathname)}${uri.slice(2)}`;
84
+ const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
85
+ return last.includes('.') // isFile
86
+ && /^[0-9]*[a-z][0-9a-z]*$/i.test(last.slice(last.lastIndexOf('.') + 1))
87
+ ? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}`
88
+ : `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
88
89
  case host.origin === source.origin
89
90
  && host.pathname === source.pathname:
90
91
  case uri.slice(0, 2) === '//':
91
92
  return uri;
92
93
  default:
93
94
  const target = new ReadonlyURL(uri, source.href);
94
- return target.origin === uri.match(/^[A-Za-z][0-9A-Za-z.+-]*:\/\/[^/?#]*/)?.[0]
95
- ? uri
96
- : target.origin === host.origin
95
+ return target.origin === host.origin
97
96
  ? target.href.slice(target.origin.length)
98
97
  : target.href;
99
98
  }
100
99
  }
101
100
 
102
- function fillTrailingSlash(pathname: string): string {
103
- assert(pathname);
104
- return pathname[pathname.length - 1] === '/'
105
- ? pathname
106
- : pathname + '/';
107
- }
108
-
109
101
  function create(
110
- address: string,
102
+ INSECURE_URI: string,
111
103
  content: readonly (string | HTMLElement)[],
112
104
  uri: ReadonlyURL,
113
105
  origin: string,
@@ -118,6 +110,12 @@ function create(
118
110
  case 'http:':
119
111
  case 'https:':
120
112
  assert(uri.host);
113
+ if (INSECURE_URI.slice(0, 2) === '^/' &&
114
+ /(?:\/\.\.?)(?:\/|$)/.test(INSECURE_URI.slice(0, INSECURE_URI.search(/[?#]|$/)))) {
115
+ type = 'argument';
116
+ description = 'Subresource paths cannot contain dot-segments.';
117
+ break;
118
+ }
121
119
  return html('a',
122
120
  {
123
121
  href: uri.source,
@@ -128,19 +126,19 @@ function create(
128
126
  : undefined,
129
127
  },
130
128
  content.length === 0
131
- ? decode(address)
129
+ ? decode(INSECURE_URI)
132
130
  : content);
133
131
  case 'tel:':
134
132
  if (content.length === 0) {
135
- content = [address];
133
+ content = [INSECURE_URI];
136
134
  }
137
135
  const pattern = /^(?:tel:)?(?:\+(?!0))?\d+(?:-\d+)*$/i;
138
136
  switch (true) {
139
137
  case content.length === 1
140
138
  && typeof content[0] === 'string'
141
- && pattern.test(address)
139
+ && pattern.test(INSECURE_URI)
142
140
  && pattern.test(content[0])
143
- && address.replace(/[^+\d]/g, '') === content[0].replace(/[^+\d]/g, ''):
141
+ && INSECURE_URI.replace(/[^+\d]/g, '') === content[0].replace(/[^+\d]/g, ''):
144
142
  return html('a', { href: uri.source }, content);
145
143
  }
146
144
  type = 'content';
@@ -155,7 +153,7 @@ function create(
155
153
  'data-invalid-description': description ??= 'Invalid protocol.',
156
154
  },
157
155
  content.length === 0
158
- ? address
156
+ ? INSECURE_URI
159
157
  : content);
160
158
  }
161
159
 
@@ -47,6 +47,8 @@ describe('Unit: parser/inline/media', () => {
47
47
  assert.deepStrictEqual(inspect(parser('![]{tel:1234567890}')), [['<img class="media invalid" data-src="tel:1234567890" alt="">'], '']);
48
48
  //assert.deepStrictEqual(inspect(parser('![]{http://[::ffff:0:0%1]}')), [['<img class="media invalid" alt="">'], '']);
49
49
  //assert.deepStrictEqual(inspect(parser('![]{http://[::ffff:0:0/96]}')), [['<img class="media invalid" alt="">'], '']);
50
+ assert.deepStrictEqual(inspect(parser('![]{./a}')), [['<img class="media invalid" data-src="./a" alt="">'], '']);
51
+ assert.deepStrictEqual(inspect(parser('![]{../a}')), [['<img class="media invalid" data-src="../a" alt="">'], '']);
50
52
  assert.deepStrictEqual(inspect(parser(' ![]{a}')), undefined);
51
53
  assert.deepStrictEqual(inspect(parser('[]{/}')), undefined);
52
54
  });
@@ -60,7 +62,6 @@ describe('Unit: parser/inline/media', () => {
60
62
  assert.deepStrictEqual(inspect(parser('![]{\\}')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt=""></a>'], '']);
61
63
  assert.deepStrictEqual(inspect(parser('![]{\\ }')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt=""></a>'], '']);
62
64
  assert.deepStrictEqual(inspect(parser('![]{\\b}')), [['<a href="\\b" target="_blank"><img class="media" data-src="\\b" alt=""></a>'], '']);
63
- assert.deepStrictEqual(inspect(parser('![]{./b}')), [['<a href="./b" target="_blank"><img class="media" data-src="./b" alt=""></a>'], '']);
64
65
  assert.deepStrictEqual(inspect(parser('![]{^/b}')), [[`<a href="/b" target="_blank"><img class="media" data-src="/b" alt=""></a>`], '']);
65
66
  assert.deepStrictEqual(inspect(parser('![ a]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
66
67
  assert.deepStrictEqual(inspect(parser('![ a ]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
@@ -33,18 +33,19 @@ export const media: MediaParser = lazy(() => creator(10, bind(verify(fmap(open(
33
33
  assert(INSECURE_URI === INSECURE_URI.trim());
34
34
  assert(!INSECURE_URI.match(/\s/));
35
35
  const url = new ReadonlyURL(
36
- resolve(INSECURE_URI, context.host || location, context.url || location),
36
+ resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
37
37
  context.host?.href || location.href);
38
- const cache = context.caches?.media;
39
- const cached = cache?.has(url.href);
40
- const el = cache && cached
41
- ? cache.get(url.href)!.cloneNode(true)
42
- : html('img', { class: 'media', 'data-src': url.source, alt: text });
43
- if (!cached && !sanitize(url, el)) return [[el], rest];
44
- cached && el.hasAttribute('alt') && el.setAttribute('alt', text);
38
+ let cache: HTMLElement | undefined;
39
+ const el = undefined
40
+ || (cache = context.caches?.media?.get(url.href)?.cloneNode(true))
41
+ || html('img', { class: 'media', 'data-src': url.source, alt: text });
42
+ assert(!el.matches('.invalid'));
43
+ if (!cache && !sanitize(url, el)) return [[el], rest];
44
+ assert(!el.matches('.invalid'));
45
+ cache?.hasAttribute('alt') && cache?.setAttribute('alt', text);
45
46
  define(el, attributes('media', push([], el.classList), optspec, params));
46
47
  assert(el.matches('img') || !el.matches('.invalid'));
47
- if (context.syntax?.inline?.link === false || cached && el.tagName !== 'IMG') return [[el], rest];
48
+ if (context.syntax?.inline?.link === false || cache && cache.tagName !== 'IMG') return [[el], rest];
48
49
  return fmap(
49
50
  link as MediaParser,
50
51
  ([link]) => [define(link, { target: '_blank' }, [el])])
@@ -66,18 +67,29 @@ const option: MediaParser.ParameterParser.OptionParser = union([
66
67
 
67
68
  function sanitize(uri: ReadonlyURL, target: HTMLElement): boolean {
68
69
  assert(target.tagName === 'IMG');
70
+ assert(!target.matches('.invalid'));
71
+ if (/^\.\.?\//.test(uri.source)) {
72
+ define(target, {
73
+ class: void target.classList.add('invalid'),
74
+ 'data-invalid-syntax': 'media',
75
+ 'data-invalid-type': 'argument',
76
+ 'data-invalid-description': 'Relative paths cannot be used with media syntax; Use subresource paths instead.',
77
+ });
78
+ return false;
79
+ }
69
80
  switch (uri.protocol) {
70
81
  case 'http:':
71
82
  case 'https:':
72
83
  assert(uri.host);
73
- return true;
84
+ break;
85
+ default:
86
+ define(target, {
87
+ class: void target.classList.add('invalid'),
88
+ 'data-invalid-syntax': 'media',
89
+ 'data-invalid-type': 'argument',
90
+ 'data-invalid-description': 'Invalid protocol.',
91
+ });
92
+ return false;
74
93
  }
75
- assert(!target.classList.contains('invalid'));
76
- define(target, {
77
- class: void target.classList.add('invalid'),
78
- 'data-invalid-syntax': 'media',
79
- 'data-invalid-type': 'argument',
80
- 'data-invalid-description': 'Invalid protocol.',
81
- });
82
- return false;
94
+ return true;
83
95
  }
@@ -3,13 +3,13 @@ import { ReferenceParser } from '../inline';
3
3
  import { union, subsequence, some, validate, verify, focus, guard, context, creator, surround, lazy, fmap } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str } from '../source';
6
- import { startLoose, isStartTight, visible, trimNode, stringify } from '../util';
6
+ import { startLoose, isStartLoose, trimNode, stringify } from '../util';
7
7
  import { html, defrag } from 'typed-dom';
8
8
 
9
9
  export const reference: ReferenceParser = lazy(() => creator(validate('[[', ']]', '\n', fmap(surround(
10
10
  '[[',
11
11
  guard(context => context.syntax?.inline?.reference ?? true,
12
- startLoose(visible(
12
+ startLoose(
13
13
  context({ syntax: { inline: {
14
14
  annotation: false,
15
15
  reference: false,
@@ -24,15 +24,15 @@ export const reference: ReferenceParser = lazy(() => creator(validate('[[', ']]'
24
24
  abbr,
25
25
  focus('^', c => [['', c], '']),
26
26
  some(inline, ']', /^\\?\n/),
27
- ]))))),
27
+ ])), ']]')),
28
28
  ']]'),
29
29
  ns => [html('sup', attributes(ns), trimNode(defrag(ns)))]))));
30
30
 
31
31
  const abbr: ReferenceParser.AbbrParser = creator(fmap(verify(surround(
32
32
  '^',
33
33
  union([str(/^(?![0-9]+\s?[|\]])[0-9A-Za-z]+(?:(?:-|(?=\W)(?!'\d)'?(?!\.\d)\.?(?!,\S),? ?)[0-9A-Za-z]+)*(?:-|'?\.?,? ?)?/)]),
34
- /^\|?(?=]])|^\|[^\S\n]+/),
35
- (_, rest, context) => isStartTight(rest, context)),
34
+ /^\|?(?=]])|^\|[^\S\n]/),
35
+ (_, rest, context) => isStartLoose(rest, context)),
36
36
  ([source]) => [html('abbr', source)]));
37
37
 
38
38
  function attributes(ns: (string | HTMLElement)[]): Record<string, string | undefined> {
@@ -70,16 +70,18 @@ function hasVisible(
70
70
  return false;
71
71
  }
72
72
 
73
- export function startLoose<P extends Parser<HTMLElement | string>>(parser: P): P;
74
- export function startLoose<T extends HTMLElement | string>(parser: Parser<T>): Parser<T> {
73
+ export function startLoose<P extends Parser<HTMLElement | string>>(parser: P, except?: string): P;
74
+ export function startLoose<T extends HTMLElement | string>(parser: Parser<T>, except?: string): Parser<T> {
75
75
  return (source, context) =>
76
- isStartLoose(source, context)
76
+ isStartLoose(source, context, except)
77
77
  ? parser(source, context)
78
78
  : undefined;
79
79
  }
80
- function isStartLoose(source: string, context: MarkdownParser.Context): boolean {
80
+ export function isStartLoose(source: string, context: MarkdownParser.Context, except?: string): boolean {
81
+ source &&= source.replace(/^[^\S\n]+/, '');
81
82
  if (source === '') return true;
82
- return isStartTight(source.replace(/^[^\S\n]+/, ''), context);
83
+ return source.slice(0, except?.length ?? 0) !== except
84
+ && isStartTight(source, context);
83
85
  }
84
86
  export function startTight<P extends Parser<unknown>>(parser: P): P;
85
87
  export function startTight<T>(parser: Parser<T>): Parser<T> {
@@ -88,7 +90,7 @@ export function startTight<T>(parser: Parser<T>): Parser<T> {
88
90
  ? parser(source, context)
89
91
  : undefined;
90
92
  }
91
- export function isStartTight(source: string, context: MarkdownParser.Context): boolean {
93
+ function isStartTight(source: string, context: MarkdownParser.Context): boolean {
92
94
  if (source === '') return true;
93
95
  switch (source[0]) {
94
96
  case ' ':
@@ -140,16 +142,6 @@ export function isEndTightNodes(nodes: readonly (HTMLElement | string)[]): boole
140
142
  : isVisible(nodes[last], -1) || last === 0 ||
141
143
  isVisible(nodes[last - 1], -1);
142
144
  }
143
- export function visible<P extends Parser<HTMLElement | string>>(parser: P): P;
144
- export function visible<T extends HTMLElement | string>(parser: Parser<T>): Parser<T> {
145
- return verify(parser, nodes => {
146
- if (nodes.length === 0) return true;
147
- for (let i = 0; i < nodes.length; ++i) {
148
- if (isVisible(nodes[i])) return true;
149
- }
150
- return false;
151
- });
152
- }
153
145
  function isVisible(node: HTMLElement | string, position?: number): boolean {
154
146
  if (!node) return false;
155
147
  switch (typeof node) {
@@ -12,7 +12,6 @@ const extend = reduce((opts: RenderingOptions): RenderingOptions =>
12
12
 
13
13
  export function render(source: HTMLElement, opts: RenderingOptions = {}): void {
14
14
  opts = extend(opts);
15
- if (source.classList.contains('invalid')) return;
16
15
  const base = location.href;
17
16
  if (source.matches(selector)) return void render_(base, source, opts);
18
17
  for (
@@ -23,7 +22,7 @@ export function render(source: HTMLElement, opts: RenderingOptions = {}): void {
23
22
  }
24
23
 
25
24
  function render_(base: string, source: HTMLElement, opts: RenderingOptions): void {
26
- assert(!source.matches('.invalid'));
25
+ if (source.classList.contains('invalid')) return;
27
26
  try {
28
27
  switch (true) {
29
28
  case !!opts.code