securemark 0.258.7 → 0.259.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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.259.0
4
+
5
+ - Extend cite syntax to accept URL.
6
+
7
+ ## 0.258.9
8
+
9
+ - Refactoring.
10
+
11
+ ## 0.258.8
12
+
13
+ - Refactoring.
14
+
3
15
  ## 0.258.7
4
16
 
5
17
  - Refactoring.
package/design.md CHANGED
@@ -285,13 +285,13 @@ CodeMirrorが素では速いがVimModeでは数万文字程度でも耐え難く
285
285
  ### バックトラック
286
286
 
287
287
  SecuremarkのAnnotation構文に典型的であるように文脈を変更する構文の中にその文脈に依存し変更される他の構文が存在する場合一般に文脈の相違から解析結果を再利用不能な(`αA'β | αAB`)バックトラックが生じる。文脈依存構文の解析中に文脈により解釈の異なる字句(`[a[`)または現在の文脈を終了する字句(ただの括弧がスコープを作らない場合の`(([a))`)に遭遇したとき、現在の文脈での解析の継続を試行する解析方法はメモ化なしではバックトラックが再帰的に生じ線形時間で解析できず、現在の文脈での解析を中止し直前の文脈を継続または新たな文脈を開始する解析方法はバックトラックなしで線形時間で解析できる。
288
- CommonMarkはLink構文から明らかなように後者の解析方法により重篤なバックトラックなしで(インラインでは)ほぼ1回の走査で解析できる言語であるが同時にこれは文脈依存構文の正当な入れ子表現(`[<a@b>]`)を解析できないか不正な表現(`[<a@b>]()`)を除外できず二重リンク(`<a><a></a></a>`)を生成する、文脈依存構文の存在する数に応じて多項式的(おそらく$n^{2+c}$)に構文の壊れやすさの増す脆く拡張性の低い言語であることを意味する(構文の決定を遅延できれば解決できるがこれにより他の構文の解釈が非決定的にならない場合に限られる)。この問題はおそらくメモ化により解決できるがCommonMarkは実行性能追及のためメモ化を廃止しており二重リンクも放置しているためメモ化により性能を低下させてまで解決するつもりはないと思われる(すなわちCommonMarkは機械を至上とし人間に制約を課す低水準の言語であり人間の需要を至上とするSecuremarkとは対極に位置する)。
289
- 従ってほぼ1回の走査で解析可能な文法に制約されるCommonMarkには文脈依存構文を入れ子表現の広範な制限ならびに構文の可読性および開始記号の信頼性の多項式的な低下と引き換えにしか追加できないという拡張性の欠陥が存在する。CommonMarkの仕様策定者が構文の拡張に(名称を維持するか否かにかかわらず)不自然なまでに消極的または進展がないのは正当な理由や怠慢からでなく文脈依存構文を追加するにつれて構文解析戦略の欠陥が明白になっていくためおよび現在の高い実行性能を低下させたくないためである。`~~a~~`のような文脈自由構文は容易に追加できるがこうしたマージンを失えばもはや後はない。
290
- Securemarkは線形時間で解析不能な前者の解析方法を各種最適化によりおおよそ4n以下の最悪計算量に改善しさらに解析時間と解析範囲の局限により一定時間内で解析不能な入力の影響を局限することでこれらの問題を解決している。この解析方法はほとんどの自然な入力に対して線形に近い時間で効率的に動作し、最悪計算量で低速に動作させる少数の機械的攻撃入力に対してもサーバーで多数のユーザーのリクエストに応じるには低速で脆弱性となる可能性があるがクライアントで単一のユーザーの操作に応じるには十分高速であるためクライアントで解析する限り解析の効率または速度が実用上問題となることはない。
288
+ CommonMarkはLink構文から明らかなように後者の解析方法により重篤なバックトラックなし(作者によると、だがHTML構文とAutolink構文では再帰的バックトラックが生じるのはないか?)で(インラインでは)ほぼ1回の走査で解析できる言語であるが同時にこれは文脈依存構文の正当な入れ子表現(`[<a@b>]`)を解析できないか不正な表現(`[<a@b>]()`)を除外できず二重リンク(`<a><a></a></a>`)を生成する、文脈依存構文の存在する数に応じて多項式的(おそらく$n^{2+c}$)に構文の壊れやすさの増す脆く拡張性の低い言語であることを意味する(構文の決定を遅延できれば解決できるがこれにより他の構文の解釈が非決定的にならない場合に限られる。そしてCommonMarkは角括弧(`<>`)の対応を取っていないためAutolink構文は自身の解釈が非決定的になると後続の構文の解釈も非決定的になる)。この問題はおそらくメモ化により解決できるがCommonMarkは実行性能追及のためメモ化を廃止しており二重リンクも放置しているためメモ化により性能を低下させてまで解決するつもりはないと思われる(すなわちCommonMarkは機械を至上とし人間に制約を課す低水準の言語であり人間の需要を至上とするSecuremarkとは対極に位置する)。
289
+ 従って現在のほぼ1回の走査で解析可能な文法に制約されるCommonMarkには文脈依存構文を入れ子表現の広範な制限ならびに構文の可読性および開始記号の信頼性の多項式的な低下と引き換えにしか追加できないという拡張性の欠陥が存在する。CommonMarkの仕様策定者が構文の拡張に(名称を維持するか否かにかかわらず)不自然なまでに消極的または進展がないのは正当な理由や怠慢からでなく文脈依存構文を追加するにつれて構文解析戦略の欠陥が明白になっていくためおよび現在の高い実行性能を低下させたくないためである。`~~a~~`のような文脈自由構文は容易に追加できるがこうしたマージンを失えばもはや後はない。CommonMarkは小さく単純であるがゆえに正しくいられる象牙の塔であり仕様策定者はこの正しさを失わず正しいままでいたいがために象牙の塔に引きこもり小さな完全性を固持し続けているのである(でなければ何年も隠さず速やかにこの拡張性の欠如を公表して助力を求めていなければならない)。
290
+ Securemarkは線形時間で解析不能な前者の解析方法をメモ化によりおおよそ4n以下の最悪計算量に改善しさらに解析時間と解析範囲の局限により一定時間内で解析不能な入力の影響を局限することでこれらの問題を解決している。この解析方法はほとんどの自然な入力に対して1nに近い時間で効率的に動作し、最悪計算量で低速に動作させる少数の機械的攻撃入力に対してもサーバーで多数のユーザーのリクエストに応じるには低速で脆弱性となる可能性があるがクライアントで単一のユーザーの操作に応じるには十分高速であるためクライアントで解析する限り解析の効率または速度が実用上問題となることはない。
291
291
 
292
292
  ### メモ化
293
293
 
294
- 一部の文脈依存言語を線形時間で解析できるようになるとしても状態数に応じて複数回走査が必要なこと、これにより時間効率が定性的には改善されても定量的には文脈自由言語より数倍悪いこと、オーバーヘッドが有意に大きいこと、時間の代わりに空間効率が非線形に悪化する可能性があることなどから理論上同じ線形時間だとしても実用上文脈自由言語と同等の実行性能にはならず文脈依存言語を文脈自由言語と等価の選択肢にするものではない。
294
+ 一部の文脈依存言語を線形時間で解析できるようになるとしても状態数に応じて複数回走査が必要なこと、このため時間効率が定性的には改善されても定量的には文脈自由言語より数倍悪いこと、オーバーヘッドが有意に大きいことなどから理論上同じ線形時間だとしても実用上文脈自由言語と同等の実行性能にはならず文脈依存言語と文脈自由言語を等価の選択肢にするものではない。
295
295
 
296
296
  ### 最適化
297
297
 
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.258.7 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.259.0 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
2
2
  (function webpackUniversalModuleDefinition(root, factory) {
3
3
  if(typeof exports === 'object' && typeof module === 'object')
4
4
  module.exports = factory(require("DOMPurify"), require("Prism"));
@@ -2884,6 +2884,7 @@ exports.syntax = syntax;
2884
2884
 
2885
2885
  function creation(cost, parser) {
2886
2886
  if (typeof cost === 'function') return creation(1, cost);
2887
+ if (cost === 0) return parser;
2887
2888
  return (source, context) => {
2888
2889
  const {
2889
2890
  resources = {
@@ -5286,6 +5287,10 @@ exports.cite = (0, combinator_1.creation)((0, combinator_1.line)((0, combinator_
5286
5287
  class: 'anchor'
5287
5288
  }, '>>.')], '']), (0, combinator_1.focus)(/^>>#\S*[^\S\n]*(?:$|\n)/, source => [[(0, dom_1.html)('a', {
5288
5289
  class: 'anchor'
5290
+ }, source)], '']), (0, combinator_1.focus)(/^>>https?:\/\/\w\S*[^\S\n]*(?:$|\n)/, source => [[(0, dom_1.html)('a', {
5291
+ class: 'anchor',
5292
+ href: source.slice(2).trimEnd(),
5293
+ target: '_blank'
5289
5294
  }, source)], ''])])]))), ([el, quotes = '']) => [(0, dom_1.html)('span', {
5290
5295
  class: 'cite'
5291
5296
  }, (0, dom_1.defrag)([`${quotes}>`, (0, dom_1.define)(el, {
@@ -5700,13 +5705,13 @@ const dom_1 = __webpack_require__(3252);
5700
5705
 
5701
5706
  exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.surround)('((', (0, combinator_1.constraint)(64
5702
5707
  /* State.annotation */
5703
- , false, (0, combinator_1.syntax)(32
5704
- /* Syntax.annotation */
5705
- , 6, 1, (0, combinator_1.state)(64
5708
+ , false, (0, combinator_1.state)(64
5706
5709
  /* State.annotation */
5707
5710
  | 2
5708
5711
  /* State.media */
5709
- , (0, visibility_1.startLoose)((0, combinator_1.context)({
5712
+ , (0, combinator_1.syntax)(32
5713
+ /* Syntax.annotation */
5714
+ , 6, 1, (0, visibility_1.startLoose)((0, combinator_1.context)({
5710
5715
  delimiters: global_1.undefined
5711
5716
  }, (0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])), ')')))), '))', false, ([, ns], rest) => [[(0, dom_1.html)('sup', {
5712
5717
  class: 'annotation'
@@ -5777,7 +5782,7 @@ const source_1 = __webpack_require__(6743);
5777
5782
  const dom_1 = __webpack_require__(3252); // https://example/@user must be a user page or a redirect page going there.
5778
5783
 
5779
5784
 
5780
- exports.account = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.open)('@', (0, combinator_1.tails)([(0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//), ([source]) => source.length <= 253 + 1), (0, combinator_1.verify)((0, source_1.str)(/^[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/), ([source]) => source.length <= 64)])), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/@')}` : `/${source}`} }`, (0, combinator_1.union)([link_1.textlink]))), ([el]) => [(0, dom_1.define)(el, {
5785
+ exports.account = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.open)('@', (0, combinator_1.tails)([(0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//), ([source]) => source.length <= 253 + 1), (0, combinator_1.verify)((0, source_1.str)(/^[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/), ([source]) => source.length <= 64)])), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/@')}` : `/${source}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
5781
5786
  class: 'account'
5782
5787
  })]));
5783
5788
 
@@ -5806,7 +5811,7 @@ const dom_1 = __webpack_require__(3252); // Timeline(pseudonym): user/tid
5806
5811
  // 外部表現は投稿ごとに投稿者の投稿時のタイムゾーンに統一する(非時系列順)
5807
5812
 
5808
5813
 
5809
- exports.anchor = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('>>', (0, combinator_1.fmap)((0, combinator_1.focus)(/^>>(?:[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*\/)?[0-9A-Za-z]+(?:-[0-9A-Za-z]+)*(?![0-9A-Za-z@#:])/, (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `/@${source.slice(2).replace('/', '/timeline/')}` : `?at=${source.slice(2)}`} }`, (0, combinator_1.union)([link_1.textlink]))), ([el]) => [(0, dom_1.define)(el, {
5814
+ exports.anchor = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('>>', (0, combinator_1.fmap)((0, combinator_1.focus)(/^>>(?:[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*\/)?[0-9A-Za-z]+(?:-[0-9A-Za-z]+)*(?![0-9A-Za-z@#:])/, (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `/@${source.slice(2).replace('/', '/timeline/')}` : `?at=${source.slice(2)}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
5810
5815
  class: 'anchor'
5811
5816
  })])));
5812
5817
 
@@ -5893,7 +5898,7 @@ const source_1 = __webpack_require__(6743);
5893
5898
 
5894
5899
  const dom_1 = __webpack_require__(3252);
5895
5900
 
5896
- exports.hashnum = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.open)('#', (0, source_1.str)(new RegExp(/^[0-9]{1,16}(?![^\p{C}\p{S}\p{P}\s]|emoji|['_])/u.source.replace(/emoji/, hashtag_1.emoji), 'u'))), (0, combinator_1.convert)(source => `[${source}]{ ${source.slice(1)} }`, (0, combinator_1.union)([link_1.textlink]))), ([el]) => [(0, dom_1.define)(el, {
5901
+ exports.hashnum = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.open)('#', (0, source_1.str)(new RegExp(/^[0-9]{1,16}(?![^\p{C}\p{S}\p{P}\s]|emoji|['_])/u.source.replace(/emoji/, hashtag_1.emoji), 'u'))), (0, combinator_1.convert)(source => `[${source}]{ ${source.slice(1)} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
5897
5902
  class: 'hashnum',
5898
5903
  href: null
5899
5904
  })]));
@@ -5922,7 +5927,7 @@ const dom_1 = __webpack_require__(3252); // https://example/hashtags/a must be a
5922
5927
 
5923
5928
 
5924
5929
  exports.emoji = String.raw`\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F`;
5925
- exports.hashtag = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.open)('#', (0, combinator_1.tails)([(0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//), ([source]) => source.length <= 253 + 1), (0, combinator_1.verify)((0, source_1.str)(new RegExp([/^(?=[0-9]{0,127}_?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji))/u.source, /(?:[^\p{C}\p{S}\p{P}\s]|emoji|_(?=[^\p{C}\p{S}\p{P}\s]|emoji)){1,128}/u.source, /(?!_?(?:[^\p{C}\p{S}\p{P}\s]|emoji)|')/u.source].join('').replace(/emoji/g, exports.emoji), 'u')), ([source]) => source.length <= 128)])), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/hashtags/')}` : `/hashtags/${source.slice(1)}`} }`, (0, combinator_1.union)([link_1.textlink]))), ([el]) => [(0, dom_1.define)(el, {
5930
+ exports.hashtag = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.open)('#', (0, combinator_1.tails)([(0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//), ([source]) => source.length <= 253 + 1), (0, combinator_1.verify)((0, source_1.str)(new RegExp([/^(?=[0-9]{0,127}_?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji))/u.source, /(?:[^\p{C}\p{S}\p{P}\s]|emoji|_(?=[^\p{C}\p{S}\p{P}\s]|emoji)){1,128}/u.source, /(?!_?(?:[^\p{C}\p{S}\p{P}\s]|emoji)|')/u.source].join('').replace(/emoji/g, exports.emoji), 'u')), ([source]) => source.length <= 128)])), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/hashtags/')}` : `/hashtags/${source.slice(1)}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
5926
5931
  class: 'hashtag'
5927
5932
  }, el.innerText)]));
5928
5933
 
@@ -5946,7 +5951,7 @@ const link_1 = __webpack_require__(9628);
5946
5951
  const source_1 = __webpack_require__(6743);
5947
5952
 
5948
5953
  const closer = /^[-+*=~^,.;:!?]*(?=[\\"`|\[\](){}<>]|$)/;
5949
- exports.url = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['http://', 'https://'], (0, combinator_1.rewrite)((0, combinator_1.open)(/^https?:\/\/(?=[\x21-\x7E])/, (0, combinator_1.focus)(/^[\x21-\x7E]+/, (0, combinator_1.some)((0, combinator_1.union)([bracket, (0, combinator_1.some)(source_1.unescsource, closer)])))), (0, combinator_1.convert)(url => `{ ${url} }`, (0, combinator_1.union)([link_1.textlink])))));
5954
+ exports.url = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['http://', 'https://'], (0, combinator_1.rewrite)((0, combinator_1.open)(/^https?:\/\/(?=[\x21-\x7E])/, (0, combinator_1.focus)(/^[\x21-\x7E]+/, (0, combinator_1.some)((0, combinator_1.union)([bracket, (0, combinator_1.some)(source_1.unescsource, closer)])))), (0, combinator_1.convert)(url => `{ ${url} }`, (0, combinator_1.union)([link_1.unsafelink])))));
5950
5955
  const bracket = (0, combinator_1.lazy)(() => (0, combinator_1.creation)((0, combinator_1.precedence)(2, (0, combinator_1.union)([(0, combinator_1.surround)('(', (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.unescsource]), ')'), ')', true), (0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.unescsource]), ']'), ']', true), (0, combinator_1.surround)('{', (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.unescsource]), '}'), '}', true), (0, combinator_1.surround)('"', (0, combinator_1.precedence)(8, (0, combinator_1.some)(source_1.unescsource, '"')), '"', true)]))));
5951
5956
 
5952
5957
  /***/ }),
@@ -6252,9 +6257,7 @@ const dom_1 = __webpack_require__(3252);
6252
6257
 
6253
6258
  exports.index = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('[#', (0, combinator_1.fmap)((0, indexee_1.indexee)((0, combinator_1.surround)('[#', (0, combinator_1.constraint)(16
6254
6259
  /* State.index */
6255
- , false, (0, combinator_1.syntax)(1024
6256
- /* Syntax.index */
6257
- , 2, 1, (0, combinator_1.state)(64
6260
+ , false, (0, combinator_1.state)(64
6258
6261
  /* State.annotation */
6259
6262
  | 32
6260
6263
  /* State.reference */
@@ -6268,7 +6271,9 @@ exports.index = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('[#', (0
6268
6271
  /* State.media */
6269
6272
  | 1
6270
6273
  /* State.autolink */
6271
- , (0, visibility_1.startTight)((0, combinator_1.open)((0, source_1.stropt)(/^\|?/), (0, visibility_1.trimBlankEnd)((0, combinator_1.some)((0, combinator_1.union)([signature, inline_1.inline]), ']', [[/^\\?\n/, 9], [']', 2]])), true))))), ']', false, ([, ns], rest) => [[(0, dom_1.html)('a', (0, dom_1.defrag)(ns))], rest])), ([el]) => [(0, dom_1.define)(el, {
6274
+ , (0, combinator_1.syntax)(1024
6275
+ /* Syntax.index */
6276
+ , 2, 1, (0, visibility_1.startTight)((0, combinator_1.open)((0, source_1.stropt)(/^\|?/), (0, visibility_1.trimBlankEnd)((0, combinator_1.some)((0, combinator_1.union)([signature, inline_1.inline]), ']', [[/^\\?\n/, 9], [']', 2]])), true))))), ']', false, ([, ns], rest) => [[(0, dom_1.html)('a', (0, dom_1.defrag)(ns))], rest])), ([el]) => [(0, dom_1.define)(el, {
6272
6277
  id: el.id ? null : global_1.undefined,
6273
6278
  class: 'index',
6274
6279
  href: el.id ? `#${el.id}` : global_1.undefined
@@ -6649,7 +6654,7 @@ exports.insertion = (0, combinator_1.lazy)(() => (0, combinator_1.surround)((0,
6649
6654
  Object.defineProperty(exports, "__esModule", ({
6650
6655
  value: true
6651
6656
  }));
6652
- exports.resolve = exports.option = exports.uri = exports.textlink = exports.link = void 0;
6657
+ exports.resolve = exports.option = exports.uri = exports.unsafelink = exports.link = void 0;
6653
6658
 
6654
6659
  const global_1 = __webpack_require__(4128);
6655
6660
 
@@ -6677,13 +6682,14 @@ const optspec = {
6677
6682
  rel: ['nofollow']
6678
6683
  };
6679
6684
  Object.setPrototypeOf(optspec, null);
6680
- exports.link = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[', '{'], (0, combinator_1.bind)((0, combinator_1.constraint)(4
6685
+ exports.link = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[', '{'], (0, combinator_1.union)([medialink, textlink])));
6686
+ const textlink = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(4
6681
6687
  /* State.link */
6682
- , false, (0, combinator_1.syntax)(256
6683
- /* Syntax.link */
6684
- , 2, 10, (0, combinator_1.fmap)((0, combinator_1.subsequence)([(0, combinator_1.state)(4
6688
+ , false, (0, combinator_1.state)(4
6685
6689
  /* State.link */
6686
- , (0, combinator_1.dup)((0, combinator_1.union)([(0, combinator_1.surround)('[', inline_1.media, ']'), (0, combinator_1.surround)('[', inline_1.shortmedia, ']'), (0, combinator_1.surround)('[', (0, combinator_1.state)(64
6690
+ | 2
6691
+ /* State.media */
6692
+ | 64
6687
6693
  /* State.annotation */
6688
6694
  | 32
6689
6695
  /* State.reference */
@@ -6691,35 +6697,52 @@ exports.link = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[', '{'
6691
6697
  /* State.index */
6692
6698
  | 8
6693
6699
  /* State.label */
6694
- | 2
6695
- /* State.media */
6696
6700
  | 1
6697
6701
  /* State.autolink */
6698
- , (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2]])), ']', true)]))), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))], nodes => nodes[0][0] !== ''), ([as, bs = []]) => bs[0] === '\r' && bs.shift() ? [as, bs] : as[0] === '\r' && as.shift() ? [[], as] : [as, []]))), ([content, params], rest, context) => {
6699
- if (content[0] === '') return [content, rest];
6700
- if (params.length === 0) return;
6702
+ , (0, combinator_1.syntax)(256
6703
+ /* Syntax.link */
6704
+ , 2, 10, (0, combinator_1.bind)((0, combinator_1.reverse)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ']', [[/^\\?\n/, 9], [']', 2]]), ']', true)), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))])), ([params, content = []], rest, context) => {
6701
6705
  if (content.length !== 0 && (0, visibility_1.trimNode)(content).length === 0) return;
6702
6706
 
6703
6707
  for (let source = (0, util_1.stringify)(content); source;) {
6704
- const result = (0, autolink_1.autolink)(source, context);
6708
+ const result = (0, combinator_1.state)(1
6709
+ /* State.autolink */
6710
+ , false, autolink_1.autolink)(source, context);
6705
6711
  if (typeof (0, parser_1.eval)(result)[0] === 'object') return;
6706
6712
  source = (0, parser_1.exec)(result);
6707
6713
  }
6708
6714
 
6715
+ return parse(content, params, rest, context);
6716
+ })))));
6717
+ const medialink = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(4
6718
+ /* State.link */
6719
+ | 2
6720
+ /* State.media */
6721
+ , false, (0, combinator_1.state)(4
6722
+ /* State.link */
6723
+ | 64
6724
+ /* State.annotation */
6725
+ | 32
6726
+ /* State.reference */
6727
+ | 16
6728
+ /* State.index */
6729
+ | 8
6730
+ /* State.label */
6731
+ | 1
6732
+ /* State.autolink */
6733
+ , (0, combinator_1.syntax)(256
6734
+ /* Syntax.link */
6735
+ , 2, 10, (0, combinator_1.bind)((0, combinator_1.reverse)((0, combinator_1.sequence)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.union)([inline_1.media, inline_1.shortmedia]), ']')), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))])), ([params, content = []], rest, context) => parse(content, params, rest, context))))));
6736
+ exports.unsafelink = (0, combinator_1.lazy)(() => (0, combinator_1.creation)(10, (0, combinator_1.precedence)(2, (0, combinator_1.bind)((0, combinator_1.reverse)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([source_1.unescsource]), ']'), ']')), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))])), ([params, content = []], rest, context) => parse(content, params, rest, context)))));
6737
+ exports.uri = (0, combinator_1.union)([(0, combinator_1.open)(/^[^\S\n]+/, (0, source_1.str)(/^\S+/)), (0, source_1.str)(/^[^\s{}]+/)]);
6738
+ exports.option = (0, combinator_1.union)([(0, combinator_1.fmap)((0, source_1.str)(/^[^\S\n]+nofollow(?=[^\S\n]|})/), () => [` rel="nofollow"`]), (0, source_1.str)(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|})/), (0, combinator_1.fmap)((0, source_1.str)(/^[^\S\n]+[^\s{}]+/), opt => [` \\${opt.slice(1)}`])]);
6739
+
6740
+ function parse(content, params, rest, context) {
6709
6741
  const INSECURE_URI = params.shift();
6710
6742
  const el = elem(INSECURE_URI, (0, dom_1.defrag)(content), new url_1.ReadonlyURL(resolve(INSECURE_URI, context.host ?? global_1.location, context.url ?? context.host ?? global_1.location), context.host?.href || global_1.location.href), context.host?.origin || global_1.location.origin);
6711
6743
  if (el.className === 'invalid') return [[el], rest];
6712
6744
  return [[(0, dom_1.define)(el, (0, html_1.attributes)('link', [], optspec, params))], rest];
6713
- })));
6714
- exports.textlink = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[', '{'], (0, combinator_1.bind)((0, combinator_1.creation)(10, (0, combinator_1.precedence)(2, (0, combinator_1.reverse)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([source_1.unescsource]), ']'), ']')), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))])))), ([params, content = []], rest, context) => {
6715
- params.shift();
6716
- (0, visibility_1.trimNode)(content);
6717
- const INSECURE_URI = params.shift();
6718
- const el = elem(INSECURE_URI, (0, dom_1.defrag)(content), new url_1.ReadonlyURL(resolve(INSECURE_URI, context.host ?? global_1.location, context.url ?? context.host ?? global_1.location), context.host?.href || global_1.location.href), context.host?.origin || global_1.location.origin);
6719
- return [[(0, dom_1.define)(el, (0, html_1.attributes)('link', [], optspec, params))], rest];
6720
- })));
6721
- exports.uri = (0, combinator_1.fmap)((0, combinator_1.union)([(0, combinator_1.open)(/^[^\S\n]+/, (0, source_1.str)(/^\S+/)), (0, source_1.str)(/^[^\s{}]+/)]), ([uri]) => ['\r', uri]);
6722
- exports.option = (0, combinator_1.union)([(0, combinator_1.fmap)((0, source_1.str)(/^[^\S\n]+nofollow(?=[^\S\n]|})/), () => [` rel="nofollow"`]), (0, source_1.str)(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|})/), (0, combinator_1.fmap)((0, source_1.str)(/^[^\S\n]+[^\s{}]+/), opt => [` \\${opt.slice(1)}`])]);
6745
+ }
6723
6746
 
6724
6747
  function resolve(uri, host, source) {
6725
6748
  switch (true) {
@@ -6899,11 +6922,11 @@ const optspec = {
6899
6922
  rel: global_1.undefined
6900
6923
  };
6901
6924
  Object.setPrototypeOf(optspec, null);
6902
- exports.media = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['![', '!{'], (0, combinator_1.bind)((0, combinator_1.verify)((0, combinator_1.fmap)((0, combinator_1.open)('!', (0, combinator_1.constraint)(2
6925
+ exports.media = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['![', '!{'], (0, combinator_1.open)('!', (0, combinator_1.constraint)(2
6903
6926
  /* State.media */
6904
6927
  , false, (0, combinator_1.syntax)(64
6905
6928
  /* Syntax.media */
6906
- , 2, 10, (0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([htmlentity_1.unsafehtmlentity, bracket, source_1.txt]), ']', [[/^\\?\n/, 9]]), ']', true)), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([link_1.uri, (0, combinator_1.some)(option)]), /^[^\S\n]*}/))])))), ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], (0, array_1.shift)(bs)[1]] : [[''], (0, array_1.shift)(as)[1]]), ([[text]]) => text === '' || text.trim() !== ''), ([[text], params], rest, context) => {
6929
+ , 2, 10, (0, combinator_1.bind)((0, combinator_1.verify)((0, combinator_1.fmap)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([htmlentity_1.unsafehtmlentity, bracket, source_1.txt]), ']', [[/^\\?\n/, 9]]), ']', true)), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([link_1.uri, (0, combinator_1.some)(option)]), /^[^\S\n]*}/))]), ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], bs] : [[''], as]), ([[text]]) => text === '' || text.trim() !== ''), ([[text], params], rest, context) => {
6907
6930
  const INSECURE_URI = params.shift();
6908
6931
  const url = new url_1.ReadonlyURL((0, link_1.resolve)(INSECURE_URI, context.host ?? global_1.location, context.url ?? context.host ?? global_1.location), context.host?.href || global_1.location.href);
6909
6932
  let cache;
@@ -6923,11 +6946,12 @@ exports.media = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['![', '
6923
6946
 
6924
6947
  if (context.state & 4
6925
6948
  /* State.link */
6926
- || cache && cache.tagName !== 'IMG') return [[el], rest];
6927
- return (0, combinator_1.fmap)(link_1.textlink, ([link]) => [(0, dom_1.define)(link, {
6949
+ ) return [[el], rest];
6950
+ if (cache && cache.tagName !== 'IMG') return (0, combinator_1.creation)(10, (..._) => [[el], rest])('!', context);
6951
+ return (0, combinator_1.fmap)(link_1.unsafelink, ([link]) => [(0, dom_1.define)(link, {
6928
6952
  target: '_blank'
6929
6953
  }, [el])])(`{ ${INSECURE_URI}${params.join('')} }${rest}`, context);
6930
- })));
6954
+ }))))));
6931
6955
  const bracket = (0, combinator_1.lazy)(() => (0, combinator_1.creation)((0, combinator_1.union)([(0, combinator_1.surround)((0, source_1.str)('('), (0, combinator_1.some)((0, combinator_1.union)([htmlentity_1.unsafehtmlentity, bracket, source_1.txt]), ')'), (0, source_1.str)(')'), true, global_1.undefined, ([as, bs = []], rest) => [(0, array_1.unshift)(as, bs), rest]), (0, combinator_1.surround)((0, source_1.str)('['), (0, combinator_1.some)((0, combinator_1.union)([htmlentity_1.unsafehtmlentity, bracket, source_1.txt]), ']'), (0, source_1.str)(']'), true, global_1.undefined, ([as, bs = []], rest) => [(0, array_1.unshift)(as, bs), rest]), (0, combinator_1.surround)((0, source_1.str)('{'), (0, combinator_1.some)((0, combinator_1.union)([htmlentity_1.unsafehtmlentity, bracket, source_1.txt]), '}'), (0, source_1.str)('}'), true, global_1.undefined, ([as, bs = []], rest) => [(0, array_1.unshift)(as, bs), rest]), (0, combinator_1.surround)((0, source_1.str)('"'), (0, combinator_1.precedence)(8, (0, combinator_1.some)((0, combinator_1.union)([htmlentity_1.unsafehtmlentity, source_1.txt]), '"')), (0, source_1.str)('"'), true)])));
6932
6956
  const option = (0, combinator_1.union)([(0, combinator_1.fmap)((0, source_1.str)(/^[^\S\n]+[1-9][0-9]*x[1-9][0-9]*(?=[^\S\n]|})/), ([opt]) => [` width="${opt.slice(1).split('x')[0]}"`, ` height="${opt.slice(1).split('x')[1]}"`]), (0, combinator_1.fmap)((0, source_1.str)(/^[^\S\n]+[1-9][0-9]*:[1-9][0-9]*(?=[^\S\n]|})/), ([opt]) => [` aspect-ratio="${opt.slice(1).split(':').join('/')}"`]), link_1.option]);
6933
6957
 
@@ -7000,15 +7024,15 @@ const dom_1 = __webpack_require__(3252);
7000
7024
 
7001
7025
  exports.reference = (0, combinator_1.lazy)(() => (0, combinator_1.surround)('[[', (0, combinator_1.constraint)(32
7002
7026
  /* State.reference */
7003
- , false, (0, combinator_1.syntax)(4096
7004
- /* Syntax.reference */
7005
- , 6, 1, (0, combinator_1.state)(64
7027
+ , false, (0, combinator_1.state)(64
7006
7028
  /* State.annotation */
7007
7029
  | 32
7008
7030
  /* State.reference */
7009
7031
  | 2
7010
7032
  /* State.media */
7011
- , (0, visibility_1.startLoose)((0, combinator_1.context)({
7033
+ , (0, combinator_1.syntax)(4096
7034
+ /* Syntax.reference */
7035
+ , 6, 1, (0, visibility_1.startLoose)((0, combinator_1.context)({
7012
7036
  delimiters: global_1.undefined
7013
7037
  }, (0, combinator_1.subsequence)([abbr, (0, combinator_1.open)((0, source_1.stropt)(/^(?=\^)/), (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])), (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])])), ']')))), ']]', false, ([, ns], rest) => [[(0, dom_1.html)('sup', attributes(ns), [(0, dom_1.html)('span', (0, visibility_1.trimNode)((0, dom_1.defrag)(ns)))])], rest]));
7014
7038
  const abbr = (0, combinator_1.creation)((0, combinator_1.bind)((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]*/), ([source], rest) => [[(0, dom_1.html)('abbr', source)], rest.replace(visibility_1.regBlankStart, '')]));
package/markdown.d.ts CHANGED
@@ -577,6 +577,7 @@ export namespace MarkdownParser {
577
577
  InlineParser.AutolinkParser.AnchorParser,
578
578
  Parser<HTMLAnchorElement, Context, []>,
579
579
  Parser<HTMLAnchorElement, Context, []>,
580
+ Parser<HTMLAnchorElement, Context, []>,
580
581
  ]>,
581
582
  ]> {
582
583
  }
@@ -660,7 +661,7 @@ export namespace MarkdownParser {
660
661
  export interface AnnotationParser extends
661
662
  // ((abc))
662
663
  Inline<'annotation'>,
663
- Parser<HTMLElement | string, Context, [
664
+ Parser<HTMLElement, Context, [
664
665
  InlineParser,
665
666
  ]> {
666
667
  }
@@ -669,7 +670,7 @@ export namespace MarkdownParser {
669
670
  // [[^abbr]]
670
671
  // [[^abbr| abc]]
671
672
  Inline<'reference'>,
672
- Parser<HTMLElement | string, Context, [
673
+ Parser<HTMLElement, Context, [
673
674
  ReferenceParser.AbbrParser,
674
675
  InlineParser,
675
676
  InlineParser,
@@ -689,7 +690,7 @@ export namespace MarkdownParser {
689
690
  export interface TemplateParser extends
690
691
  // {{abc}}
691
692
  Inline<'template'>,
692
- Parser<HTMLSpanElement | string, Context, [
693
+ Parser<HTMLSpanElement, Context, [
693
694
  TemplateParser.BracketParser,
694
695
  SourceParser.EscapableSourceParser,
695
696
  ]> {
@@ -842,21 +843,38 @@ export namespace MarkdownParser {
842
843
  // { uri }
843
844
  // [abc]{uri nofollow}
844
845
  Inline<'link'>,
845
- Parser<HTMLElement | string, Context, [
846
- LinkParser.ContentParser,
847
- LinkParser.ParameterParser,
848
- ]> {
849
- }
850
- export interface TextLinkParser extends
851
- // { uri }
852
- // [abc]{uri nofollow}
853
- Inline<'textlink'>,
854
846
  Parser<HTMLAnchorElement, Context, [
855
- LinkParser.TextParser,
856
- LinkParser.ParameterParser,
847
+ LinkParser.MediaLinkParser,
848
+ LinkParser.TextLinkParser,
857
849
  ]> {
858
850
  }
859
851
  export namespace LinkParser {
852
+ export interface TextLinkParser extends
853
+ Inline<'link/textlink'>,
854
+ Parser<HTMLAnchorElement, Context, [
855
+ Parser<(HTMLElement | string)[], Context, [
856
+ InlineParser,
857
+ ]>,
858
+ LinkParser.ParameterParser,
859
+ ]> {
860
+ }
861
+ export interface MediaLinkParser extends
862
+ Inline<'link/medialink'>,
863
+ Parser<HTMLAnchorElement, Context, [
864
+ Parser<HTMLElement[], Context, [
865
+ MediaParser,
866
+ ShortmediaParser,
867
+ ]>,
868
+ LinkParser.ParameterParser,
869
+ ]> {
870
+ }
871
+ export interface UnsafeLinkParser extends
872
+ Inline<'link/unsafelink'>,
873
+ Parser<HTMLAnchorElement, Context, [
874
+ LinkParser.TextParser,
875
+ LinkParser.ParameterParser,
876
+ ]> {
877
+ }
860
878
  export interface ContentParser extends
861
879
  Inline<'link/content'>,
862
880
  Parser<(HTMLElement | string)[], Context, [
@@ -1093,7 +1111,7 @@ export namespace MarkdownParser {
1093
1111
  // https://host
1094
1112
  Inline<'url'>,
1095
1113
  Parser<HTMLAnchorElement, Context, [
1096
- TextLinkParser,
1114
+ LinkParser.UnsafeLinkParser,
1097
1115
  ]> {
1098
1116
  }
1099
1117
  export namespace UrlParser {
@@ -1135,28 +1153,28 @@ export namespace MarkdownParser {
1135
1153
  // @user
1136
1154
  Inline<'account'>,
1137
1155
  Parser<HTMLAnchorElement, Context, [
1138
- TextLinkParser,
1156
+ LinkParser.UnsafeLinkParser,
1139
1157
  ]> {
1140
1158
  }
1141
1159
  export interface HashtagParser extends
1142
1160
  // #tag
1143
1161
  Inline<'hashtag'>,
1144
1162
  Parser<HTMLAnchorElement, Context, [
1145
- TextLinkParser,
1163
+ LinkParser.UnsafeLinkParser,
1146
1164
  ]> {
1147
1165
  }
1148
1166
  export interface HashnumParser extends
1149
1167
  // #1
1150
1168
  Inline<'hashnum'>,
1151
1169
  Parser<HTMLAnchorElement, Context, [
1152
- TextLinkParser,
1170
+ LinkParser.UnsafeLinkParser,
1153
1171
  ]> {
1154
1172
  }
1155
1173
  export interface AnchorParser extends
1156
1174
  // >>1
1157
1175
  Inline<'anchor'>,
1158
1176
  Parser<HTMLAnchorElement, Context, [
1159
- TextLinkParser,
1177
+ LinkParser.UnsafeLinkParser,
1160
1178
  ]> {
1161
1179
  }
1162
1180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.258.7",
3
+ "version": "0.259.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",
@@ -89,6 +89,7 @@ export function creation<P extends Parser<unknown>>(parser: P): P;
89
89
  export function creation<P extends Parser<unknown>>(cost: number, parser: P): P;
90
90
  export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
91
91
  if (typeof cost === 'function') return creation(1, cost);
92
+ if (cost === 0) return parser!;
92
93
  assert(cost >= 0);
93
94
  return (source, context) => {
94
95
  const { resources = { clock: 1, recursion: 1 } } = context;
@@ -18,7 +18,7 @@ describe('Unit: parser/block/reply/cite', () => {
18
18
  assert.deepStrictEqual(inspect(parser('>>\\')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('>>01#')), undefined);
20
20
  assert.deepStrictEqual(inspect(parser('>>01@')), undefined);
21
- assert.deepStrictEqual(inspect(parser('>>https://host')), undefined);
21
+ assert.deepStrictEqual(inspect(parser('>>http://')), undefined);
22
22
  assert.deepStrictEqual(inspect(parser('>>tel:1234567890')), undefined);
23
23
  assert.deepStrictEqual(inspect(parser('>>..')), undefined);
24
24
  assert.deepStrictEqual(inspect(parser('>> 0')), undefined);
@@ -43,6 +43,8 @@ describe('Unit: parser/block/reply/cite', () => {
43
43
  assert.deepStrictEqual(inspect(parser('>>#a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#a</a></span>', '<br>'], '']);
44
44
  assert.deepStrictEqual(inspect(parser('>>#index:a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#index:a</a></span>', '<br>'], '']);
45
45
  assert.deepStrictEqual(inspect(parser('>>#:~:text=a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#:~:text=a</a></span>', '<br>'], '']);
46
+ assert.deepStrictEqual(inspect(parser('>>http://host')), [['<span class="cite">&gt;<a class="anchor" href="http://host" target="_blank" data-depth="1">&gt;http://host</a></span>', '<br>'], '']);
47
+ assert.deepStrictEqual(inspect(parser('>>https://host')), [['<span class="cite">&gt;<a class="anchor" href="https://host" target="_blank" data-depth="1">&gt;https://host</a></span>', '<br>'], '']);
46
48
  });
47
49
 
48
50
  });
@@ -14,6 +14,7 @@ export const cite: ReplyParser.CiteParser = creation(line(fmap(validate(
14
14
  // リンクの実装は後で検討
15
15
  focus(/^>>\.[^\S\n]*(?:$|\n)/, () => [[html('a', { class: 'anchor' }, '>>.')], '']),
16
16
  focus(/^>>#\S*[^\S\n]*(?:$|\n)/, source => [[html('a', { class: 'anchor' }, source)], '']),
17
+ focus(/^>>https?:\/\/\w\S*[^\S\n]*(?:$|\n)/, source => [[html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source)], '']),
17
18
  ]),
18
19
  ]))),
19
20
  ([el, quotes = '']: [HTMLElement, string?]) => [
@@ -9,8 +9,8 @@ import { html, defrag } from 'typed-dom/dom';
9
9
  export const annotation: AnnotationParser = lazy(() => surround(
10
10
  '((',
11
11
  constraint(State.annotation, false,
12
- syntax(Syntax.annotation, 6, 1,
13
12
  state(State.annotation | State.media,
13
+ syntax(Syntax.annotation, 6, 1,
14
14
  startLoose(
15
15
  context({ delimiters: undefined },
16
16
  some(union([inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])), ')')))),
@@ -1,6 +1,6 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, tails, verify, rewrite, open, convert, fmap, lazy } from '../../../combinator';
3
- import { textlink } from '../link';
3
+ import { unsafelink } from '../link';
4
4
  import { str } from '../../source';
5
5
  import { define } from 'typed-dom/dom';
6
6
 
@@ -24,5 +24,5 @@ export const account: AutolinkParser.AccountParser = lazy(() => fmap(rewrite(
24
24
  ? `https://${source.slice(1).replace('/', '/@')}`
25
25
  : `/${source}`
26
26
  } }`,
27
- union([textlink]))),
27
+ union([unsafelink]))),
28
28
  ([el]) => [define(el, { class: 'account' })]));
@@ -1,6 +1,6 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, validate, focus, convert, fmap, lazy } from '../../../combinator';
3
- import { textlink } from '../link';
3
+ import { unsafelink } from '../link';
4
4
  import { define } from 'typed-dom/dom';
5
5
 
6
6
  // Timeline(pseudonym): user/tid
@@ -21,5 +21,5 @@ export const anchor: AutolinkParser.AnchorParser = lazy(() => validate('>>', fma
21
21
  ? `/@${source.slice(2).replace('/', '/timeline/')}`
22
22
  : `?at=${source.slice(2)}`
23
23
  } }`,
24
- union([textlink]))),
24
+ union([unsafelink]))),
25
25
  ([el]) => [define(el, { class: 'anchor' })])));
@@ -1,6 +1,6 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, rewrite, open, convert, fmap, lazy } from '../../../combinator';
3
- import { textlink } from '../link';
3
+ import { unsafelink } from '../link';
4
4
  import { emoji } from './hashtag';
5
5
  import { str } from '../../source';
6
6
  import { define } from 'typed-dom/dom';
@@ -9,5 +9,5 @@ export const hashnum: AutolinkParser.HashnumParser = lazy(() => fmap(rewrite(
9
9
  open('#', str(new RegExp(/^[0-9]{1,16}(?![^\p{C}\p{S}\p{P}\s]|emoji|['_])/u.source.replace(/emoji/, emoji), 'u'))),
10
10
  convert(
11
11
  source => `[${source}]{ ${source.slice(1)} }`,
12
- union([textlink]))),
12
+ union([unsafelink]))),
13
13
  ([el]) => [define(el, { class: 'hashnum', href: null })]));
@@ -1,6 +1,6 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, tails, verify, rewrite, open, convert, fmap, lazy } from '../../../combinator';
3
- import { textlink } from '../link';
3
+ import { unsafelink } from '../link';
4
4
  import { str } from '../../source';
5
5
  import { define } from 'typed-dom/dom';
6
6
 
@@ -31,5 +31,5 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => fmap(rewrite(
31
31
  ? `https://${source.slice(1).replace('/', '/hashtags/')}`
32
32
  : `/hashtags/${source.slice(1)}`
33
33
  } }`,
34
- union([textlink]))),
34
+ union([unsafelink]))),
35
35
  ([el]) => [define(el, { class: 'hashtag' }, el.innerText)]));
@@ -1,6 +1,6 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, some, creation, precedence, validate, focus, rewrite, convert, surround, open, lazy } from '../../../combinator';
3
- import { textlink } from '../link';
3
+ import { unsafelink } from '../link';
4
4
  import { unescsource } from '../../source';
5
5
 
6
6
  const closer = /^[-+*=~^,.;:!?]*(?=[\\"`|\[\](){}<>]|$)/;
@@ -11,7 +11,7 @@ export const url: AutolinkParser.UrlParser = lazy(() => validate(['http://', 'ht
11
11
  focus(/^[\x21-\x7E]+/, some(union([bracket, some(unescsource, closer)])))),
12
12
  convert(
13
13
  url => `{ ${url} }`,
14
- union([textlink])))));
14
+ union([unsafelink])))));
15
15
 
16
16
  const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => creation(precedence(2, union([
17
17
  surround('(', some(union([bracket, unescsource]), ')'), ')', true),
@@ -13,8 +13,8 @@ import IndexParser = ExtensionParser.IndexParser;
13
13
  export const index: IndexParser = lazy(() => validate('[#', fmap(indexee(surround(
14
14
  '[#',
15
15
  constraint(State.index, false,
16
- syntax(Syntax.index, 2, 1,
17
16
  state(State.annotation | State.reference | State.index | State.label | State.link | State.media | State.autolink,
17
+ syntax(Syntax.index, 2, 1,
18
18
  startTight(
19
19
  open(stropt(/^\|?/), trimBlankEnd(some(union([
20
20
  signature,
@@ -1,7 +1,8 @@
1
1
  import { undefined, location, encodeURI, decodeURI, Location } from 'spica/global';
2
- import { LinkParser, TextLinkParser } from '../inline';
3
- import { eval, exec } from '../../combinator/data/parser';
4
- import { union, inits, tails, subsequence, some, constraint, syntax, creation, precedence, state, validate, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
2
+ import { MarkdownParser } from '../../../markdown';
3
+ import { LinkParser } from '../inline';
4
+ import { Result, eval, exec } from '../../combinator/data/parser';
5
+ import { union, inits, tails, sequence, some, constraint, syntax, creation, precedence, state, validate, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
5
6
  import { inline, media, shortmedia } from '../inline';
6
7
  import { attributes } from './html';
7
8
  import { autolink } from '../autolink';
@@ -17,81 +18,64 @@ const optspec = {
17
18
  } as const;
18
19
  Object.setPrototypeOf(optspec, null);
19
20
 
20
- export const link: LinkParser = lazy(() => validate(['[', '{'], bind(
21
+ export const link: LinkParser = lazy(() => validate(['[', '{'], union([
22
+ medialink,
23
+ textlink,
24
+ ])));
25
+
26
+ const textlink: LinkParser.TextLinkParser = lazy(() =>
21
27
  constraint(State.link, false,
28
+ state(State.link | State.media | State.annotation | State.reference | State.index | State.label | State.autolink,
22
29
  syntax(Syntax.link, 2, 10,
23
- fmap(subsequence([
24
- state(State.link,
25
- dup(union([
26
- surround('[', media, ']'),
27
- surround('[', shortmedia, ']'),
28
- surround(
29
- '[',
30
- state(State.annotation | State.reference | State.index | State.label | State.media | State.autolink,
31
- some(inline, ']', [[/^\\?\n/, 9], [']', 2]])),
32
- ']',
33
- true),
34
- ]))),
30
+ bind(reverse(tails([
31
+ dup(surround(
32
+ '[',
33
+ some(union([inline]), ']', [[/^\\?\n/, 9], [']', 2]]),
34
+ ']',
35
+ true)),
35
36
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
36
- ], nodes => nodes[0][0] !== ''),
37
- ([as, bs = []]) => bs[0] === '\r' && bs.shift() ? [as, bs] : as[0] === '\r' && as.shift() ? [[], as] : [as, []]))),
38
- ([content, params]: [(HTMLElement | string)[], string[]], rest, context) => {
39
- assert(content[0] !== '' || params.length === 0);
40
- if (content[0] === '') return [content, rest];
41
- if (params.length === 0) return;
42
- assert(params.every(p => typeof p === 'string'));
37
+ ])),
38
+ ([params, content = []]: [string[], (HTMLElement | string)[]], rest, context) => {
39
+ assert(!html('div', content).querySelector('a, .media, .annotation, .reference'));
43
40
  if (content.length !== 0 && trimNode(content).length === 0) return;
44
41
  for (let source = stringify(content); source;) {
45
- const result = autolink(source, context);
42
+ const result = state(State.autolink, false, autolink)(source, context);
46
43
  if (typeof eval(result!)[0] === 'object') return;
47
44
  source = exec(result!);
48
45
  }
49
- assert(!html('div', content).querySelector('a, .media, .annotation, .reference') || (content[0] as HTMLElement).matches('.media'));
50
- const INSECURE_URI = params.shift()!;
51
- assert(INSECURE_URI === INSECURE_URI.trim());
52
- assert(!INSECURE_URI.match(/\s/));
53
- const el = elem(
54
- INSECURE_URI,
55
- defrag(content),
56
- new ReadonlyURL(
57
- resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
58
- context.host?.href || location.href),
59
- context.host?.origin || location.origin);
60
- if (el.className === 'invalid') return [[el], rest];
61
- assert(el.classList.length === 0);
62
- return [[define(el, attributes('link', [], optspec, params))], rest];
63
- })));
46
+ return parse(content, params, rest, context);
47
+ })))));
48
+
49
+ const medialink: LinkParser.MediaLinkParser = lazy(() =>
50
+ constraint(State.link | State.media, false,
51
+ state(State.link | State.annotation | State.reference | State.index | State.label | State.autolink,
52
+ syntax(Syntax.link, 2, 10,
53
+ bind(reverse(sequence([
54
+ dup(surround(
55
+ '[',
56
+ union([media, shortmedia]),
57
+ ']')),
58
+ dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
59
+ ])),
60
+ ([params, content = []]: [string[], (HTMLElement | string)[]], rest, context) =>
61
+ parse(content, params, rest, context))))));
64
62
 
65
- export const textlink: TextLinkParser = lazy(() => validate(['[', '{'], bind(
63
+ export const unsafelink: LinkParser.UnsafeLinkParser = lazy(() =>
66
64
  creation(10, precedence(2,
67
- reverse(tails([
68
- dup(surround('[', some(union([unescsource]), ']'), ']')),
65
+ bind(reverse(tails([
66
+ dup(surround(
67
+ '[',
68
+ some(union([unescsource]), ']'),
69
+ ']')),
69
70
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
70
- ])))),
71
- ([params, content = []], rest, context) => {
72
- assert(params[0] === '\r');
73
- params.shift();
74
- assert(params.every(p => typeof p === 'string'));
75
- trimNode(content);
76
- const INSECURE_URI = params.shift()!;
77
- assert(INSECURE_URI === INSECURE_URI.trim());
78
- assert(!INSECURE_URI.match(/\s/));
79
- const el = elem(
80
- INSECURE_URI,
81
- defrag(content),
82
- new ReadonlyURL(
83
- resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
84
- context.host?.href || location.href),
85
- context.host?.origin || location.origin);
86
- assert(el.className !== 'invalid');
87
- assert(el.classList.length === 0);
88
- return [[define(el, attributes('link', [], optspec, params))], rest];
89
- })));
71
+ ])),
72
+ ([params, content = []], rest, context) =>
73
+ parse(content, params, rest, context)))));
90
74
 
91
- export const uri: LinkParser.ParameterParser.UriParser = fmap(union([
75
+ export const uri: LinkParser.ParameterParser.UriParser = union([
92
76
  open(/^[^\S\n]+/, str(/^\S+/)),
93
77
  str(/^[^\s{}]+/),
94
- ]), ([uri]) => ['\r', uri]);
78
+ ]);
95
79
 
96
80
  export const option: LinkParser.ParameterParser.OptionParser = union([
97
81
  fmap(str(/^[^\S\n]+nofollow(?=[^\S\n]|})/), () => [` rel="nofollow"`]),
@@ -99,6 +83,29 @@ export const option: LinkParser.ParameterParser.OptionParser = union([
99
83
  fmap(str(/^[^\S\n]+[^\s{}]+/), opt => [` \\${opt.slice(1)}`]),
100
84
  ]);
101
85
 
86
+ function parse(
87
+ content: (string | HTMLElement)[],
88
+ params: string[],
89
+ rest: string,
90
+ context: MarkdownParser.Context,
91
+ ): Result<HTMLAnchorElement, MarkdownParser.Context> {
92
+ assert(params.length > 0);
93
+ assert(params.every(p => typeof p === 'string'));
94
+ const INSECURE_URI = params.shift()!;
95
+ assert(INSECURE_URI === INSECURE_URI.trim());
96
+ assert(!INSECURE_URI.match(/\s/));
97
+ const el = elem(
98
+ INSECURE_URI,
99
+ defrag(content),
100
+ new ReadonlyURL(
101
+ resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
102
+ context.host?.href || location.href),
103
+ context.host?.origin || location.origin);
104
+ if (el.className === 'invalid') return [[el], rest];
105
+ assert(el.classList.length === 0);
106
+ return [[define(el, attributes('link', [], optspec, params))], rest];
107
+ }
108
+
102
109
  export function resolve(uri: string, host: URL | Location, source: URL | Location): string {
103
110
  assert(uri);
104
111
  assert(uri === uri.trim());
@@ -1,14 +1,14 @@
1
1
  import { undefined, location } from 'spica/global';
2
2
  import { MediaParser } from '../inline';
3
3
  import { union, inits, tails, some, syntax, creation, precedence, constraint, validate, verify, surround, open, dup, lazy, fmap, bind } from '../../combinator';
4
- import { textlink, uri, option as linkoption, resolve } from './link';
4
+ 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, str } from '../source';
8
8
  import { Syntax, State } from '../context';
9
9
  import { html, define } from 'typed-dom/dom';
10
10
  import { ReadonlyURL } from 'spica/url';
11
- import { unshift, shift, push } from 'spica/array';
11
+ import { unshift, push } from 'spica/array';
12
12
 
13
13
  const optspec = {
14
14
  'width': [],
@@ -18,19 +18,19 @@ const optspec = {
18
18
  } as const;
19
19
  Object.setPrototypeOf(optspec, null);
20
20
 
21
- export const media: MediaParser = lazy(() => validate(['![', '!{'], bind(verify(fmap(open(
21
+ export const media: MediaParser = lazy(() => validate(['![', '!{'], open(
22
22
  '!',
23
23
  constraint(State.media, false,
24
24
  syntax(Syntax.media, 2, 10,
25
- tails([
25
+ bind(verify(fmap(tails([
26
26
  dup(surround(
27
27
  '[',
28
28
  some(union([unsafehtmlentity, bracket, txt]), ']', [[/^\\?\n/, 9]]),
29
29
  ']',
30
30
  true)),
31
31
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
32
- ])))),
33
- ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], shift(bs)[1]] : [[''], shift(as)[1]]),
32
+ ]),
33
+ ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], bs] : [[''], as]),
34
34
  ([[text]]) => text === '' || text.trim() !== ''),
35
35
  ([[text], params], rest, context) => {
36
36
  assert(text === text.trim());
@@ -54,12 +54,13 @@ export const media: MediaParser = lazy(() => validate(['![', '!{'], bind(verify(
54
54
  if (el.hasAttribute('aspect-ratio')) {
55
55
  el.style.aspectRatio = el.getAttribute('aspect-ratio')!;
56
56
  }
57
- if (context.state! & State.link || cache && cache.tagName !== 'IMG') return [[el], rest];
57
+ if (context.state! & State.link) return [[el], rest];
58
+ if (cache && cache.tagName !== 'IMG') return creation(10, (..._) => [[el!], rest])('!', context);
58
59
  return fmap(
59
- textlink as MediaParser,
60
+ unsafelink as MediaParser,
60
61
  ([link]) => [define(link, { target: '_blank' }, [el])])
61
62
  (`{ ${INSECURE_URI}${params.join('')} }${rest}`, context);
62
- })));
63
+ }))))));
63
64
 
64
65
  const bracket: MediaParser.TextParser.BracketParser = lazy(() => creation(union([
65
66
  surround(str('('), some(union([unsafehtmlentity, bracket, txt]), ')'), str(')'), true, undefined, ([as, bs = []], rest) => [unshift(as, bs), rest]),
@@ -11,8 +11,8 @@ import { html, defrag } from 'typed-dom/dom';
11
11
  export const reference: ReferenceParser = lazy(() => surround(
12
12
  '[[',
13
13
  constraint(State.reference, false,
14
- syntax(Syntax.reference, 6, 1,
15
14
  state(State.annotation | State.reference | State.media,
15
+ syntax(Syntax.reference, 6, 1,
16
16
  startLoose(
17
17
  context({ delimiters: undefined },
18
18
  subsequence([
@@ -34,7 +34,6 @@ export import MathParser = InlineParser.MathParser;
34
34
  export import ExtensionParser = InlineParser.ExtensionParser;
35
35
  export import RubyParser = InlineParser.RubyParser;
36
36
  export import LinkParser = InlineParser.LinkParser;
37
- export import TextLinkParser = InlineParser.TextLinkParser;
38
37
  export import HTMLParser = InlineParser.HTMLParser;
39
38
  export import InsertionParser = InlineParser.InsertionParser;
40
39
  export import DeletionParser = InlineParser.DeletionParser;