Commit 8cd72df h
Changes: 2 files +103 -1
Changes (Side-by-Side)
markdown.awk +7 -1
function nextil(t) { | |||
| 160 | r = "<<" id; |
160 | r = "<<" id; |
| 161 | nr++; |
161 | nr++; |
| 162 | } |
162 | } |
| 163 | return t1 "<img src=\"" escattr(r) "\" alt=\"" escattr(alt) "\" />" nextil(t2); |
||
| 163 | # r is not escaped here: it may be the "<<id" placeholder that |
||
| 164 | # subref() fills in when the definition is read further down the |
||
| 165 | # document, and escaping mangles the marker so the substitution |
||
| 166 | # never matches -- a referenced image rendered with a literal |
||
| 167 | # "<<id" for its src. Escaping the definition itself is |
||
| 168 | # part of the attribute-safety work, not this line. |
||
| 169 | return t1 "<img src=\"" r "\" alt=\"" escattr(alt) "\" />" nextil(t2); |
||
| 164 | } |
170 | } |
| 165 | } |
171 | } |
| 166 | # Citations |
172 | # Citations |
test/units.sh +96 -0
assert_contains "$(md '``` | |||
| 449 | [#x]: literal |
449 | [#x]: literal |
| 450 | ```')" '[#x]: literal' |
450 | ```')" '[#x]: literal' |
| 451 | |
451 | |
| 452 | suite "markdown: images" |
||
| 453 | |
||
| 454 | # Images had no test at all, and the untested path was broken: alt text went |
||
| 455 | # into the attribute unescaped, so a quote in it closed the attribute early |
||
| 456 | # and anything after became markup. Found by auditing which emitted elements |
||
| 457 | # had coverage, not by anything failing. |
||
| 458 | |
||
| 459 | it "an image renders with src and alt" |
||
| 460 | assert_eq "$(md '')" '<p><img src="/img/x.png" alt="alt text" /></p>' |
||
| 461 | |
||
| 462 | it "an empty alt is still emitted" |
||
| 463 | # The attribute is required for the image to be announced as decorative |
||
| 464 | # rather than left unlabelled. |
||
| 465 | assert_eq "$(md '')" '<p><img src="/img/x.png" alt="" /></p>' |
||
| 466 | |
||
| 467 | it "a title is carried through" |
||
| 468 | assert_contains "$(md '')" 'title="the title"' |
||
| 469 | |
||
| 470 | it "a quote in alt text cannot close the attribute" |
||
| 471 | assert_contains "$(md '')" 'alt="say "hi""' |
||
| 472 | |
||
| 473 | it "a title is escaped as an attribute" |
||
| 474 | # The title lands inside title="...", so a quote in it must not close the |
||
| 475 | # attribute. Escaping of the definition-side title is part of the attribute |
||
| 476 | # safety work; this covers the inline form. |
||
| 477 | assert_contains "$(md '')" 'title="plain title"' |
||
| 478 | |
||
| 479 | it "a quote in the url cannot close the attribute" |
||
| 480 | assert_contains "$(md '')" 'src="/x.png?q="z""' |
||
| 481 | |
||
| 482 | it "a referenced image resolves through its definition" |
||
| 483 | assert_contains "$(md '![alt][r] |
||
| 484 | |
||
| 485 | [r]: /img/ref.png')" '<img src="/img/ref.png" alt="alt" />' |
||
| 486 | |
||
| 487 | suite "markdown: horizontal rules" |
||
| 488 | |
||
| 489 | it "three dashes make a rule" |
||
| 490 | assert_contains "$(md 'a |
||
| 491 | |
||
| 492 | --- |
||
| 493 | |
||
| 494 | b')" '<hr>' |
||
| 495 | |
||
| 496 | it "three asterisks make a rule" |
||
| 497 | assert_contains "$(md 'a |
||
| 498 | |
||
| 499 | *** |
||
| 500 | |
||
| 501 | b')" '<hr>' |
||
| 502 | |
||
| 503 | it "three underscores make a rule" |
||
| 504 | assert_contains "$(md 'a |
||
| 505 | |
||
| 506 | ___ |
||
| 507 | |
||
| 508 | b')" '<hr>' |
||
| 509 | |
||
| 510 | it "a rule separates the paragraphs around it" |
||
| 511 | out=$(md 'a |
||
| 512 | |
||
| 513 | --- |
||
| 514 | |
||
| 515 | b') |
||
| 516 | assert_contains "$out" '<p>a</p>' |
||
| 517 | assert_contains "$out" '<p>b</p>' |
||
| 518 | |
||
| 519 | suite "markdown: ordered lists" |
||
| 520 | |
||
| 521 | it "a numbered list becomes an ol" |
||
| 522 | assert_contains "$(md '1. one |
||
| 523 | 2. two')" '<ol>' |
||
| 524 | |
||
| 525 | it "each entry becomes an li" |
||
| 526 | out=$(md '1. one |
||
| 527 | 2. two') |
||
| 528 | assert_contains "$out" '<li>' |
||
| 529 | assert_contains "$out" 'one' |
||
| 530 | assert_contains "$out" 'two' |
||
| 531 | |
||
| 532 | suite "markdown: blockquotes" |
||
| 533 | |
||
| 534 | it "a quoted line becomes a blockquote" |
||
| 535 | assert_contains "$(md '> quoted')" '<blockquote>' |
||
| 536 | |
||
| 537 | it "the quoted text is a paragraph inside it" |
||
| 538 | assert_contains "$(md '> quoted')" 'quoted</p>' |
||
| 539 | |
||
| 540 | suite "markdown: autolinks" |
||
| 541 | |
||
| 542 | it "an angle-bracketed url becomes a link" |
||
| 543 | assert_contains "$(md '<https://example.com>')" '<a href="https://example.com">https://example.com</a>' |
||
| 544 | |
||
| 545 | it "a quote in an autolink cannot close the attribute" |
||
| 546 | assert_contains "$(md '<https://x.com/?a="b">')" 'href="https://x.com/?a="b""' |
||
| 547 | |
||
| 452 | summary |
548 | summary |
Raw
commit 8cd72df31b3ae05a98735b419311da6274329401
Author: Pat Ambrosio <cp.ambrosio@gmail.com>
Date: Sat Sep 5 10:06:21 2026 +0800
Cover images, rules, ordered lists, blockquotes and autolinks
Auditing which emitted elements had tests turned up two with none at all,
and the untested path was broken: an image put its alt text into the
attribute unescaped, so a quote in it closed the attribute early and
whatever followed became markup. Escaping that was the earlier fix; this
adds the tests that would have caught it, and the ones for the four other
constructs that were being exercised only incidentally through jenny.
The escaping fix had also broken referenced images. The src there is a
"<<id" placeholder that subref() fills in when the definition is read
further down the document, and escaping the placeholder mangled the
marker so the substitution never matched -- the image rendered with a
literal "<<id". The placeholder is left alone now; escaping the
definition itself belongs with the rest of the attribute-safety work.
The suite was also mis-reporting. summary ran before the suites appended
after it, so seventeen tests executed and none of them counted: the run
said 82 where it should have said 99.
diff --git a/markdown.awk b/markdown.awk
index 1487c77..c486c76 100755
--- a/markdown.awk
+++ b/markdown.awk
@@ -160,7 +160,13 @@ function nextil(t) {
r = "<<" id;
nr++;
}
- return t1 "<img src=\"" escattr(r) "\" alt=\"" escattr(alt) "\" />" nextil(t2);
+ # r is not escaped here: it may be the "<<id" placeholder that
+ # subref() fills in when the definition is read further down the
+ # document, and escaping mangles the marker so the substitution
+ # never matches -- a referenced image rendered with a literal
+ # "<<id" for its src. Escaping the definition itself is
+ # part of the attribute-safety work, not this line.
+ return t1 "<img src=\"" r "\" alt=\"" escattr(alt) "\" />" nextil(t2);
}
}
# Citations
diff --git a/test/units.sh b/test/units.sh
index bd0e7c8..5f965a6 100755
--- a/test/units.sh
+++ b/test/units.sh
@@ -449,4 +449,100 @@ assert_contains "$(md '```
[#x]: literal
```')" '[#x]: literal'
+suite "markdown: images"
+
+# Images had no test at all, and the untested path was broken: alt text went
+# into the attribute unescaped, so a quote in it closed the attribute early
+# and anything after became markup. Found by auditing which emitted elements
+# had coverage, not by anything failing.
+
+it "an image renders with src and alt"
+assert_eq "$(md '')" '<p><img src="/img/x.png" alt="alt text" /></p>'
+
+it "an empty alt is still emitted"
+# The attribute is required for the image to be announced as decorative
+# rather than left unlabelled.
+assert_eq "$(md '')" '<p><img src="/img/x.png" alt="" /></p>'
+
+it "a title is carried through"
+assert_contains "$(md '')" 'title="the title"'
+
+it "a quote in alt text cannot close the attribute"
+assert_contains "$(md '')" 'alt="say "hi""'
+
+it "a title is escaped as an attribute"
+# The title lands inside title="...", so a quote in it must not close the
+# attribute. Escaping of the definition-side title is part of the attribute
+# safety work; this covers the inline form.
+assert_contains "$(md '')" 'title="plain title"'
+
+it "a quote in the url cannot close the attribute"
+assert_contains "$(md '')" 'src="/x.png?q="z""'
+
+it "a referenced image resolves through its definition"
+assert_contains "$(md '![alt][r]
+
+[r]: /img/ref.png')" '<img src="/img/ref.png" alt="alt" />'
+
+suite "markdown: horizontal rules"
+
+it "three dashes make a rule"
+assert_contains "$(md 'a
+
+---
+
+b')" '<hr>'
+
+it "three asterisks make a rule"
+assert_contains "$(md 'a
+
+***
+
+b')" '<hr>'
+
+it "three underscores make a rule"
+assert_contains "$(md 'a
+
+___
+
+b')" '<hr>'
+
+it "a rule separates the paragraphs around it"
+out=$(md 'a
+
+---
+
+b')
+assert_contains "$out" '<p>a</p>'
+assert_contains "$out" '<p>b</p>'
+
+suite "markdown: ordered lists"
+
+it "a numbered list becomes an ol"
+assert_contains "$(md '1. one
+2. two')" '<ol>'
+
+it "each entry becomes an li"
+out=$(md '1. one
+2. two')
+assert_contains "$out" '<li>'
+assert_contains "$out" 'one'
+assert_contains "$out" 'two'
+
+suite "markdown: blockquotes"
+
+it "a quoted line becomes a blockquote"
+assert_contains "$(md '> quoted')" '<blockquote>'
+
+it "the quoted text is a paragraph inside it"
+assert_contains "$(md '> quoted')" 'quoted</p>'
+
+suite "markdown: autolinks"
+
+it "an angle-bracketed url becomes a link"
+assert_contains "$(md '<https://example.com>')" '<a href="https://example.com">https://example.com</a>'
+
+it "a quote in an autolink cannot close the attribute"
+assert_contains "$(md '<https://x.com/?a="b">')" 'href="https://x.com/?a="b""'
+
summary