ssh git@grit.deadkey.org man

Commit 8cd72df h

Pat Ambrosio · 26-09-05 10:06

Cover images, rules, ordered lists, blockquotes and autolinks

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 # "&lt;&lt;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 '![alt text](/img/x.png)')" '<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 '![](/img/x.png)')" '<p><img src="/img/x.png" alt="" /></p>'
466
467 it "a title is carried through"
468 assert_contains "$(md '![a](/x.png "the title")')" 'title="the title"'
469
470 it "a quote in alt text cannot close the attribute"
471 assert_contains "$(md '![say "hi"](/x.png)')" 'alt="say &quot;hi&quot;"'
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 '![a](/x.png "plain title")')" 'title="plain title"'
478
479 it "a quote in the url cannot close the attribute"
480 assert_contains "$(md '![a](/x.png?q="z")')" 'src="/x.png?q=&quot;z&quot;"'
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=&quot;b&quot;"'
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 "&lt;&lt;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
+			# "&lt;&lt;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 '![alt text](/img/x.png)')" '<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 '![](/img/x.png)')" '<p><img src="/img/x.png" alt="" /></p>'
+
+it "a title is carried through"
+assert_contains "$(md '![a](/x.png "the title")')" 'title="the title"'
+
+it "a quote in alt text cannot close the attribute"
+assert_contains "$(md '![say "hi"](/x.png)')" 'alt="say &quot;hi&quot;"'
+
+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 '![a](/x.png "plain title")')" 'title="plain title"'
+
+it "a quote in the url cannot close the attribute"
+assert_contains "$(md '![a](/x.png?q="z")')" 'src="/x.png?q=&quot;z&quot;"'
+
+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=&quot;b&quot;"'
+
 summary