Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,18 @@ GFM 그대로, 정렬 표시와 셀 안의 마크업까지 살려서 낸다.
허용 스킴일 때만). 다른 채널의 `![alt](url)` 은 텔레그램·plain 은 링크, 마크다운 채널은 그대로
- 스킴 — 허용 목록. 주면 **그것만** 받는다(기본값에 더하지 않는다). React 컴포넌트도 같은 목록으로
한 번 더 본다
- `<img>`의 `alt`는 인라인 서식을 제거한 일반 텍스트를 속성값으로 이스케이프한다. 이미지가
링크로 내려갈 때는 라벨의 본문 서식을 유지한다. 렌더된 HTML을 `alt`에 넣으면 태그가
대체 글에 비치고, 이벤트 토크나이저가 속성 안의 `>`를 태그 끝으로 읽는다(리뷰에서 재현).
- 링크·이미지 라벨의 인라인 파싱은 32단계까지 재귀한다. 그보다 깊은 라벨은 원문을 해당
출력 문맥의 글자로 이스케이프해 낸다 — 내용은 남기고 호출 스택 고갈을 막는다. 대괄호
짝과 주소 끝은 `[`가 든 렌더 구간(스트리밍 조각, 링크 라벨)마다 한 번 역순으로 색인하고,
본문 색인 버퍼는 조각 사이에 재사용한다. 닫히지 않은 `[`마다 줄 끝까지 다시 훑으면 입력
크기의 제곱만큼 걸리므로, 실패한 후보도 색인에서 바로 판정한다.
- HTML 출력에서 실제로 `<a>`가 되는 링크 라벨 안의 링크·오토링크·이미지 링크 폴백은 라벨과
주소를 글자로 보존한다. 바깥 링크가 차단된 스킴이거나 한도를 넘어 글자로 나가면 안쪽
링크는 그대로 링크다. `<a>` 안에 `<a>`를 만들면 브라우저가 바깥 링크를 중간에서 닫으며,
텔레그램도 중첩 링크를 받지 않는다. 명시적으로 불러오는 `<img>`는 링크 안에도 둔다.
- 원문 태그는 강조와 같은 스택에 올라 짝·중첩이 맞는다 — 짝 없는 닫는 태그는 버리고, 안 닫힌
여는 태그는 블록 끝에서 닫는다(GitHub 도 같다). 퍼즈가 `<sub>` 만 열고 끝난 입력에서 잡았다
- 한도가 없어 나누지 않는다. **스트리밍은 텔레그램 편집과 같은 계약이다** — 누적본에
Expand Down
3 changes: 3 additions & 0 deletions corpus/cases/blocked-outer-link-label/github-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[docs](https://ok.test)](javascript:x)

[<https://in.test>](javascript:x)
3 changes: 3 additions & 0 deletions corpus/cases/blocked-outer-link-label/html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<p><a href="https://ok.test">docs</a> (javascript:x)</p>

<p><a href="https://in.test">https://in.test</a> (javascript:x)</p>
3 changes: 3 additions & 0 deletions corpus/cases/blocked-outer-link-label/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[docs](https://ok.test)](javascript:x)

[<https://in.test>](javascript:x)
3 changes: 3 additions & 0 deletions corpus/cases/blocked-outer-link-label/notion-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[docs](https://ok.test)](javascript:x)

[[https://in.test](https://in.test)](javascript:x)
3 changes: 3 additions & 0 deletions corpus/cases/blocked-outer-link-label/plain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
docs (https://ok.test) (javascript:x)

https://in.test (javascript:x)
3 changes: 3 additions & 0 deletions corpus/cases/blocked-outer-link-label/slack-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[docs](https://ok.test)](javascript:x)

[<https://in.test>](javascript:x)
3 changes: 3 additions & 0 deletions corpus/cases/blocked-outer-link-label/why.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
리뷰에서 재현했다. 안쪽 링크를 글자로 내릴지 라벨 깊이만 보고 정해서, 바깥 링크가 차단된
스킴이라 `<a>`가 되지 않는데도 안쪽의 정상 링크까지 글자로 내려갔다. 링크가 하나도 없는
출력이 됐다. 안쪽 링크는 바깥이 실제로 `<a>`를 낼 때만 글자로 내린다.
3 changes: 3 additions & 0 deletions corpus/cases/cell-link-label-break/github-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| a | b |
| --- | --- |
| [x<br>y](https://e.test) | z |
4 changes: 4 additions & 0 deletions corpus/cases/cell-link-label-break/html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<table>
<thead><tr><th>a</th><th>b</th></tr></thead>
<tbody><tr><td><a href="https://e.test">x<br>y</a></td><td>z</td></tr></tbody>
</table>
3 changes: 3 additions & 0 deletions corpus/cases/cell-link-label-break/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| a | b |
|---|---|
| [x<br>y](https://e.test) | z |
10 changes: 10 additions & 0 deletions corpus/cases/cell-link-label-break/notion-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<table header-row="true">
<tr>
<td>a</td>
<td>b</td>
</tr>
<tr>
<td>[x<br>y](https://e.test)</td>
<td>z</td>
</tr>
</table>
3 changes: 3 additions & 0 deletions corpus/cases/cell-link-label-break/plain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
a | b
-------------------- | -
x y (https://e.test) | z
3 changes: 3 additions & 0 deletions corpus/cases/cell-link-label-break/slack-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| a | b |
| --- | --- |
| [x y](https://e.test) | z |
3 changes: 3 additions & 0 deletions corpus/cases/cell-link-label-break/telegram-html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<pre>a | b
-------------------- | -
x y (https://e.test) | z</pre>
2 changes: 2 additions & 0 deletions corpus/cases/cell-link-label-break/why.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
리뷰에서 재현했다. 링크 라벨을 렌더하는 안쪽 인라인 상태가 표 칸 안이라는 것을 이어받지
않아, 라벨의 `<br>`이 실제 줄바꿈이 됐다. 표 행이 둘로 갈라지고 링크도 깨졌다.
1 change: 1 addition & 0 deletions corpus/cases/deep-target-label/github-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[cat](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)
1 change: 1 addition & 0 deletions corpus/cases/deep-target-label/html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p><a href="https://e.test">[[[[[[[cat](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test)</a></p>
1 change: 1 addition & 0 deletions corpus/cases/deep-target-label/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[cat](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)
1 change: 1 addition & 0 deletions corpus/cases/deep-target-label/notion-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[cat](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)
1 change: 1 addition & 0 deletions corpus/cases/deep-target-label/plain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[cat](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test)
1 change: 1 addition & 0 deletions corpus/cases/deep-target-label/slack-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[cat](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)
1 change: 1 addition & 0 deletions corpus/cases/deep-target-label/telegram-html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<a href="https://e.test">[[[[[[[cat](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test)](https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test) (https://e.test)</a>
6 changes: 6 additions & 0 deletions corpus/cases/deep-target-label/why.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
리뷰에서 링크 라벨 안의 링크를 재귀해 읽는 깊이가 제한되지 않았다.
Rust CLI는 1만 단계 입력에서 호출 스택을 고갈시키고 프로세스를 중단했다.

라벨 파싱은 32단계 뒤에 원문 글자로 내려간다. 이 케이스는 한도를 넘었을 때의
출력을 채널마다 지키며, 더 큰 Rust·Go 회귀 시험은 완성본과 스트리밍을 대조한다.
HTML 출력은 안쪽 주소를 글자로 내려 중첩 <a>를 만들지 않는다.
1 change: 1 addition & 0 deletions corpus/cases/image-alt-format/github-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
사진 ![**cat** & `x<y>` "q"](https://e.test/p.png)
1 change: 1 addition & 0 deletions corpus/cases/image-alt-format/html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>사진 <a href="https://e.test/p.png"><strong>cat</strong> &amp; <code>x&lt;y&gt;</code> "q"</a></p>
1 change: 1 addition & 0 deletions corpus/cases/image-alt-format/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
사진 ![**cat** & `x<y>` "q"](https://e.test/p.png)
1 change: 1 addition & 0 deletions corpus/cases/image-alt-format/notion-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
사진 ![**cat** & `x<y>` "q"](https://e.test/p.png)
1 change: 1 addition & 0 deletions corpus/cases/image-alt-format/plain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
사진 cat & x<y> "q" (https://e.test/p.png)
1 change: 1 addition & 0 deletions corpus/cases/image-alt-format/slack-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
사진 ![**cat** & `x<y>` "q"](https://e.test/p.png)
1 change: 1 addition & 0 deletions corpus/cases/image-alt-format/telegram-html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
사진 <a href="https://e.test/p.png"><b>cat</b> &amp; <code>x&lt;y&gt;</code> "q"</a>
7 changes: 7 additions & 0 deletions corpus/cases/image-alt-format/why.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
리뷰에서 `html.images = load`로 `![**cat**](https://e.test/p.png)`를 렌더하면
`alt="<strong>cat</strong>"`가 나왔다. 대체 글에 태그가 비치고, npm 이벤트 토크나이저는
속성 안의 첫 `>`에서 태그를 끊어 React에서 이미지 대신 `<img …` 글자를 그렸다.

불러오는 이미지의 대체 글만 일반 텍스트로 읽고 속성값으로 이스케이프한다.
이 코퍼스는 기본 옵션의 링크 폴백이 서식을 유지하는지 지킨다. 불러오기 옵션의
Rust·Go 시험과 npm 스모크는 서식 제거·특수문자·이벤트·React 출력을 함께 확인한다.
5 changes: 5 additions & 0 deletions corpus/cases/nested-link-label/github-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[[**cat**](https://inner.test)](https://outer.test)

[<https://inner.test>](https://outer.test)

[![**cat**](https://inner.test/p.png)](https://outer.test)
5 changes: 5 additions & 0 deletions corpus/cases/nested-link-label/html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<p><a href="https://outer.test"><strong>cat</strong> (https://inner.test)</a></p>

<p><a href="https://outer.test">https://inner.test</a></p>

<p><a href="https://outer.test"><strong>cat</strong> (https://inner.test/p.png)</a></p>
5 changes: 5 additions & 0 deletions corpus/cases/nested-link-label/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[[**cat**](https://inner.test)](https://outer.test)

[<https://inner.test>](https://outer.test)

[![**cat**](https://inner.test/p.png)](https://outer.test)
5 changes: 5 additions & 0 deletions corpus/cases/nested-link-label/notion-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[[**cat**](https://inner.test)](https://outer.test)

[[https://inner.test](https://inner.test)](https://outer.test)

[![**cat**](https://inner.test/p.png)](https://outer.test)
5 changes: 5 additions & 0 deletions corpus/cases/nested-link-label/plain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cat (https://inner.test) (https://outer.test)

https://inner.test (https://outer.test)

cat (https://inner.test/p.png) (https://outer.test)
5 changes: 5 additions & 0 deletions corpus/cases/nested-link-label/slack-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[[**cat**](https://inner.test)](https://outer.test)

[<https://inner.test>](https://outer.test)

[![**cat**](https://inner.test/p.png)](https://outer.test)
5 changes: 5 additions & 0 deletions corpus/cases/nested-link-label/telegram-html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<a href="https://outer.test"><b>cat</b> (https://inner.test)</a>

<a href="https://outer.test">https://inner.test</a>

<a href="https://outer.test"><b>cat</b> (https://inner.test/p.png)</a>
7 changes: 7 additions & 0 deletions corpus/cases/nested-link-label/why.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
리뷰에서 링크 라벨의 인라인 파싱이 안쪽 링크도 `<a>`로 냈다. HTML에서는 `<a>`를
중첩할 수 없어 브라우저가 바깥 링크를 중간에서 닫으며, React도 잘못된 중첩을 받는다.
기본 옵션의 이미지도 링크로 내려가므로 링크 안의 이미지가 같은 경로를 탄다.

HTML 출력에서는 안쪽 라벨의 서식과 주소를 보존하되 링크 태그는 만들지 않는다.
오토링크는 주소를 중복하지 않는다. 명시적으로 불러오는 이미지는 `<img>`로 유지한다
(Rust·Go 옵션 시험). 마크다운 출력은 기존 표기를 유지한다.
1 change: 1 addition & 0 deletions corpus/cases/unclosed-link-brackets/github-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[broken [**ok**](https://e.test)
1 change: 1 addition & 0 deletions corpus/cases/unclosed-link-brackets/html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[broken <a href="https://e.test"><strong>ok</strong></a></p>
1 change: 1 addition & 0 deletions corpus/cases/unclosed-link-brackets/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[broken [**ok**](https://e.test)
1 change: 1 addition & 0 deletions corpus/cases/unclosed-link-brackets/notion-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[broken [**ok**](https://e.test)
1 change: 1 addition & 0 deletions corpus/cases/unclosed-link-brackets/plain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[broken ok (https://e.test)
1 change: 1 addition & 0 deletions corpus/cases/unclosed-link-brackets/slack-markdown.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[broken [**ok**](https://e.test)
1 change: 1 addition & 0 deletions corpus/cases/unclosed-link-brackets/telegram-html.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[broken <a href="https://e.test"><b>ok</b></a>
6 changes: 6 additions & 0 deletions corpus/cases/unclosed-link-brackets/why.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
리뷰에서 닫히지 않은 `[`를 반복한 한 줄은 각 대괄호에서 남은 꼬리를 다시 훑었다.
Rust CLI의 1만·2만·4만 자 입력이 약 0.19초·0.74초·3.00초로 늘어났다.
스트리밍의 붙들기 탐색은 이어서 훑어도, 마지막 인라인 파싱에서 제곱 비용이 발생했다.

대괄호 짝과 주소 끝을 한 번 색인한다. 짝 없는 대괄호 뒤의 유효한 링크도 찾아야 한다.
큰 입력의 Rust·Go 회귀 시험은 완성본과 스트리밍 결과가 같은지 확인한다.
Loading
Loading