Fossil

Check-in [f4ff013a]
Login

Check-in [f4ff013a]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Impose a limit on the depth of nesting of inline footnotes. Also add a few test cases: for depth limiting and HTML hijacking.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | markdown-footnotes
Files: files | file ages | folders
SHA3-256: f4ff013ace5f1c557b9089133c0874b11f21b51a8aa5ebcd4092471a5a46ae34
User & Date: george 2022-02-21 04:29:46
Context
2022-02-21
05:14
Add a comment for append_footnote_upc(). Also substitute a variable of zero value with just "0" constant. No functional changes. ... (check-in: ae8a3dd5 user: george tags: markdown-footnotes)
04:29
Impose a limit on the depth of nesting of inline footnotes. Also add a few test cases: for depth limiting and HTML hijacking. ... (check-in: f4ff013a user: george tags: markdown-footnotes)
2022-02-20
23:00
If there are issues with footnotes then set TH1 variable $footnotes_issues_counters to a space separated list of integers that count for "misref", "unref" and "joins". This eliminates the need for JavaScript for the case when a custom skin wants to warn about issues with footnotes in the header of a page.
Also fix counting of "joins": count the number of unique labels that have multiple definitions (and not the number of such definitions).
... (check-in: 773cef5c user: george tags: markdown-footnotes)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/default.css.

1681
1682
1683
1684
1685
1686
1687

1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706




1707
1708
1709
1710
1711
1712



1713
1714
1715
1716
1717
1718
1719
div.markdown ol.footnotes > li.fn-joined > sup.fn-joined::after {
  content: "(joined from multiple locations) ";
}
div.markdown ol.footnotes > li.fn-misreference {
  margin-top:    0.75em;
  margin-bottom: 0.75em;
}

div.markdown ol.footnotes > li.fn-misreference,
div.markdown ol.footnotes > li.fn-unreferenced {
  color: gray;
}
div.markdown ol.footnotes > li.fn-misreference > span {
  color: red;
}
div.markdown ol.footnotes > li.fn-misreference > span::after {
  content: " (use of undefined label).";
}
div.markdown ol.footnotes > li.fn-unreferenced {
  padding-left: 0.5em;
}
div.markdown ol.footnotes > li.fn-unreferenced > code {
  color: red;
}
div.markdown ol.footnotes > li.fn-unreferenced > i::after {
  content: " was defined but is not referenced";
}




div.markdown ol.footnotes > li.fn-unreferenced > pre {
  color: gray;
  font-size: 85%;
  padding-left: 0.5em;
  margin-top:  0.25em;
  border-left: 2px solid red;



}
div.markdown > ol.footnotes > li > .fn-backrefs {
  margin-right: 0.5em;
  font-weight: bold;
}
div.markdown > ol.footnotes > li > .fn-backrefs > a,
div.markdown sup.noteref > a {







>



















>
>
>
>






>
>
>







1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
div.markdown ol.footnotes > li.fn-joined > sup.fn-joined::after {
  content: "(joined from multiple locations) ";
}
div.markdown ol.footnotes > li.fn-misreference {
  margin-top:    0.75em;
  margin-bottom: 0.75em;
}
div.markdown ol.footnotes > li.fn-toodeep > i,
div.markdown ol.footnotes > li.fn-misreference,
div.markdown ol.footnotes > li.fn-unreferenced {
  color: gray;
}
div.markdown ol.footnotes > li.fn-misreference > span {
  color: red;
}
div.markdown ol.footnotes > li.fn-misreference > span::after {
  content: " (use of undefined label).";
}
div.markdown ol.footnotes > li.fn-unreferenced {
  padding-left: 0.5em;
}
div.markdown ol.footnotes > li.fn-unreferenced > code {
  color: red;
}
div.markdown ol.footnotes > li.fn-unreferenced > i::after {
  content: " was defined but is not referenced";
}
div.markdown ol.footnotes > li.fn-toodeep > i::after {
  content: " depth of nesting of inline footnotes exceeded the limit";
}
div.markdown ol.footnotes > li.fn-toodeep > pre,
div.markdown ol.footnotes > li.fn-unreferenced > pre {
  color: gray;
  font-size: 85%;
  padding-left: 0.5em;
  margin-top:  0.25em;
  border-left: 2px solid red;
}
div.markdown ol.footnotes > li.fn-toodeep > pre {
  margin-left: 0.5em;
}
div.markdown > ol.footnotes > li > .fn-backrefs {
  margin-right: 0.5em;
  font-weight: bold;
}
div.markdown > ol.footnotes > li > .fn-backrefs > a,
div.markdown sup.noteref > a {

Changes to src/markdown.c.

2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
  parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));

  if( (blob_size(allNotes) || rndr.notes.misref.nUsed) ){

    /* Footnotes must be parsed for the correct discovery of (back)links */
    Blob *notes = new_work_buffer( &rndr );
    Blob *tmp   = new_work_buffer( &rndr );
    int nMarks = -1;

    /* inline notes may get appended to rndr.notes.all while rendering */
    while(1){
      struct footnote *aNotes;
      const int N = COUNT_FOOTNOTES( allNotes );

      /* make a shallow copy of `origin` */
      blob_truncate(notes,0);
      blob_append(notes, blob_buffer(allNotes), blob_size(allNotes));
      aNotes = CAST_AS_FOOTNOTES(notes);
      qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort);

      if( nMarks == rndr.notes.nMarks ) break;
      nMarks = rndr.notes.nMarks;

      for(i=0; i<N; i++){
        const int j = aNotes[i].index;
        struct footnote *x = CAST_AS_FOOTNOTES(allNotes) + j;
        assert( 0<=j && j<N );
        if( x->bRndred || !x->nUsed ) continue;







|












|







2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
  parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));

  if( (blob_size(allNotes) || rndr.notes.misref.nUsed) ){

    /* Footnotes must be parsed for the correct discovery of (back)links */
    Blob *notes = new_work_buffer( &rndr );
    Blob *tmp   = new_work_buffer( &rndr );
    int nMarks = -1, maxDepth = 5;

    /* inline notes may get appended to rndr.notes.all while rendering */
    while(1){
      struct footnote *aNotes;
      const int N = COUNT_FOOTNOTES( allNotes );

      /* make a shallow copy of `origin` */
      blob_truncate(notes,0);
      blob_append(notes, blob_buffer(allNotes), blob_size(allNotes));
      aNotes = CAST_AS_FOOTNOTES(notes);
      qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort);

      if( --maxDepth < 0 || nMarks == rndr.notes.nMarks ) break;
      nMarks = rndr.notes.nMarks;

      for(i=0; i<N; i++){
        const int j = aNotes[i].index;
        struct footnote *x = CAST_AS_FOOTNOTES(allNotes) + j;
        assert( 0<=j && j<N );
        if( x->bRndred || !x->nUsed ) continue;
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802

    /* footnotes rendering */
    if( rndr.make.footnote_item && rndr.make.footnotes ){
      Blob *all_items = new_work_buffer(&rndr);
      int j = -1;
      for(i=0; i<COUNT_FOOTNOTES(notes); i++){
        const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i;
        if( x->bRndred ){
          rndr.make.footnote_item(all_items, &x->text, x->iMark,
                                  x->nUsed, rndr.make.opaque);
          j = i;
        }
      }
      if( rndr.notes.misref.nUsed ){
        rndr.make.footnote_item(all_items, 0, -1,
                    rndr.notes.misref.nUsed, rndr.make.opaque);
        g.ftntsIssues[0] += rndr.notes.misref.nUsed;
      }
      while( ++j < COUNT_FOOTNOTES(notes) ){
        const struct footnote* x = CAST_AS_FOOTNOTES(notes) + j;
        assert( !x->nUsed );
        assert( !x->bRndred );
        assert( (&x->id) + 1 == &x->text ); /* see html_footnote_item() */
        assert( (&x->upc)- 1 == &x->text );
        rndr.make.footnote_item(all_items,&x->text,0,0,rndr.make.opaque);
        g.ftntsIssues[1]++;
      }
      rndr.make.footnotes(ob, all_items, rndr.make.opaque);
      release_work_buffer(&rndr, all_items);
    }
    release_work_buffer(&rndr, notes);
  }







|

|










|



|







2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802

    /* footnotes rendering */
    if( rndr.make.footnote_item && rndr.make.footnotes ){
      Blob *all_items = new_work_buffer(&rndr);
      int j = -1;
      for(i=0; i<COUNT_FOOTNOTES(notes); i++){
        const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i;
        if( x->iMark ){
          rndr.make.footnote_item(all_items, &x->text, x->iMark,
                   x->bRndred ? x->nUsed : 0, rndr.make.opaque);
          j = i;
        }
      }
      if( rndr.notes.misref.nUsed ){
        rndr.make.footnote_item(all_items, 0, -1,
                    rndr.notes.misref.nUsed, rndr.make.opaque);
        g.ftntsIssues[0] += rndr.notes.misref.nUsed;
      }
      while( ++j < COUNT_FOOTNOTES(notes) ){
        const struct footnote* x = CAST_AS_FOOTNOTES(notes) + j;
        assert( !x->iMark );
        assert( !x->bRndred );
        assert( (&x->id) + 1 == &x->text ); /* see html_footnote_item() */
        assert( (&x->upc)- 1 == &x->text );
        rndr.make.footnote_item(all_items,&x->text,x->iMark,0,rndr.make.opaque);
        g.ftntsIssues[1]++;
      }
      rndr.make.footnotes(ob, all_items, rndr.make.opaque);
      release_work_buffer(&rndr, all_items);
    }
    release_work_buffer(&rndr, notes);
  }

Changes to src/markdown_html.c.

466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481





482
483

484
485
486
487
488
489


490

491
492
493
494
495
496
497
498
        BLOB_APPEND_URI(ob, ctx);
        blob_appendf(ob,"#misref%s-%c'>%c</a>", unique,c, c);
      }
      if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
    }
    BLOB_APPEND_LITERAL(ob,"</sup>\n<span>Misreference</span>");

  }else if( nUsed ){                   /* a regular footnote */
    char pos[24];
    const char *join = "";
    /* make.footnote_item() invocations should pass args accordingly */
    const struct Blob * upc = text+1;
    #define _joined_footnote_indicator "<ul class='fn-joined'>"
    #define _jfi_sz (sizeof(_joined_footnote_indicator)-1)
    assert( text );
    assert( blob_size(text) );





    if( blob_size(text)>=_jfi_sz &&
       !memcmp(blob_buffer(text),_joined_footnote_indicator,_jfi_sz)){

      join = "fn-joined ";
    }
    memset(pos,0,24);
    sprintf(pos, "%s-%i", unique, iMark);
    blob_appendf(ob, "<li id='footnote%s' class='%s", pos, join);
    append_footnote_upc(ob, upc, 0);




    if( nUsed == 1 ){
      BLOB_APPEND_LITERAL(ob, "fn-monoref'><sup class='fn-backrefs'>");
      blob_appendf(ob,"<a id='footnote%s-a' href='", pos);
      BLOB_APPEND_URI(ob, ctx);
      blob_appendf(ob,"#noteref%s-a'>^</a>", pos);
    }else{
      int i;
      BLOB_APPEND_LITERAL(ob, "fn-polyref'><sup class='fn-backrefs'>^");







|

|






>
>
>
>
>
|
|
>
|
|
<
<
<
|
>
>
|
>
|







466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491



492
493
494
495
496
497
498
499
500
501
502
503
504
        BLOB_APPEND_URI(ob, ctx);
        blob_appendf(ob,"#misref%s-%c'>%c</a>", unique,c, c);
      }
      if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
    }
    BLOB_APPEND_LITERAL(ob,"</sup>\n<span>Misreference</span>");

  }else if( iMark > 0 ){               /* a regular footnote */
    char pos[24];
    int bJoin = 0;
    /* make.footnote_item() invocations should pass args accordingly */
    const struct Blob * upc = text+1;
    #define _joined_footnote_indicator "<ul class='fn-joined'>"
    #define _jfi_sz (sizeof(_joined_footnote_indicator)-1)
    assert( text );
    assert( blob_size(text) );
    memset(pos,0,24);
    sprintf(pos, "%s-%i", unique, iMark);

    blob_appendf(ob, "<li id='footnote%s' class='", pos);
    if( nUsed ){
      if( blob_size(text)>=_jfi_sz &&
         !memcmp(blob_buffer(text),_joined_footnote_indicator,_jfi_sz)){
        bJoin = 1;
        BLOB_APPEND_LITERAL(ob, "fn-joined ");
      }



      append_footnote_upc(ob, upc, 0);
    }else{
      BLOB_APPEND_LITERAL(ob, "fn-toodeep ");
    }

    if( nUsed <= 1 ){
      BLOB_APPEND_LITERAL(ob, "fn-monoref'><sup class='fn-backrefs'>");
      blob_appendf(ob,"<a id='footnote%s-a' href='", pos);
      BLOB_APPEND_URI(ob, ctx);
      blob_appendf(ob,"#noteref%s-a'>^</a>", pos);
    }else{
      int i;
      BLOB_APPEND_LITERAL(ob, "fn-polyref'><sup class='fn-backrefs'>^");
510
511
512
513
514
515
516
517
518
519
520
521





522
523
524
525
526
527
528

529
530
531
532
533
534
535
        BLOB_APPEND_URI(ob, ctx);
        blob_appendf(ob,"#noteref%s-%s'>%s</a>", pos,l.c, l.c);
      }
      if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
    }
    BLOB_APPEND_LITERAL(ob,"</sup>\n");
    append_footnote_upc(ob, upc, 1);
    if( join[0] ){
      BLOB_APPEND_LITERAL(ob,"<sup class='fn-joined'></sup><ul>");
      blob_append(ob,blob_buffer(text)+_jfi_sz,blob_size(text)-_jfi_sz);
    }else{
      BLOB_APPEND_BLOB(ob, text);





    }
    #undef _joined_footnote_indicator
    #undef _jfi_sz
  }else{
    /* a footnote was defined but wasn't used */
    /* make.footnote_item() invocations should pass args accordingly */
    const struct Blob * id = text-1;

    assert( text );
    assert( blob_size(text) );
    assert( blob_size(id) );
    BLOB_APPEND_LITERAL(ob,"<li class='fn-unreferenced'>\n[^&nbsp;<code>");
    html_escape(ob, blob_buffer(id), blob_size(id));
    BLOB_APPEND_LITERAL(ob, "</code>&nbsp;]<i></i>\n"
        "<pre><code class='language-markdown'>");







|


|

>
>
>
>
>







>







516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
        BLOB_APPEND_URI(ob, ctx);
        blob_appendf(ob,"#noteref%s-%s'>%s</a>", pos,l.c, l.c);
      }
      if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
    }
    BLOB_APPEND_LITERAL(ob,"</sup>\n");
    append_footnote_upc(ob, upc, 1);
    if( bJoin ){
      BLOB_APPEND_LITERAL(ob,"<sup class='fn-joined'></sup><ul>");
      blob_append(ob,blob_buffer(text)+_jfi_sz,blob_size(text)-_jfi_sz);
    }else if( nUsed ){
      BLOB_APPEND_BLOB(ob, text);
    }else{
      BLOB_APPEND_LITERAL(ob,"<i></i>\n"
          "<pre><code class='language-markdown'>");
      html_escape(ob, blob_buffer(text), blob_size(text));
      BLOB_APPEND_LITERAL(ob,"</code></pre>");
    }
    #undef _joined_footnote_indicator
    #undef _jfi_sz
  }else{
    /* a footnote was defined but wasn't used */
    /* make.footnote_item() invocations should pass args accordingly */
    const struct Blob * id = text-1;
    assert( !nUsed );
    assert( text );
    assert( blob_size(text) );
    assert( blob_size(id) );
    BLOB_APPEND_LITERAL(ob,"<li class='fn-unreferenced'>\n[^&nbsp;<code>");
    html_escape(ob, blob_buffer(id), blob_size(id));
    BLOB_APPEND_LITERAL(ob, "</code>&nbsp;]<i></i>\n"
        "<pre><code class='language-markdown'>");

Changes to test/markdown-test3.md.

121
122
123
124
125
126
127












































128
129
130
131
132
133
134
in the stylesheet.[^nostyle]
If a footnote consists just of a valid userclass token then this token
is not interpreted as such, instead it is emitted as plain text.
(^  
   .bare.classlist.inside.inline.footnote:  
)[^bare1]
[^bare2]













































## Footnotes

[branch]: /timeline?r=markdown-footnotes&nowiki

[^ 1]:  Footnotes is a Fossil' extention of
        Markdown. Your other tools may have limited support for these.







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
in the stylesheet.[^nostyle]
If a footnote consists just of a valid userclass token then this token
is not interpreted as such, instead it is emitted as plain text.
(^  
   .bare.classlist.inside.inline.footnote:  
)[^bare1]
[^bare2]

<html>
  Click
  <a href="?a=B&quote='&nonASCII=😂&script=<script>alert('Broken!');</script>">
  here</a> and
  <a href='?a=B&quote="&nonASCII=😂&script=<script>alert("Broken!");</script>'>
  here</a>
  to test escaping of REQUEST_URI in the generated footnote markers.
</html>

A depth of nesting must be limited.
(^
 A long chain of nested inline footnotes...
 (^
  is a rather unusual thing...
  (^
   and requires extra CPU cycles for processing.
   (^
    Theoretically speaking O(n<sup>2</sup>).
    (^
     Thus it is worth dismissing those footnotes...
     (^
      that are nested deeper than on a certain level.
      (^
       A particular value for that limit...
       (^
        is hard-coded in src/markdown.c ...
        (^
         in function `markdown()` ...
         (^
          in variable named `maxDepth`.
          (^
           For the time being, its value is **5**
          )
         )
        )
       )
      )
     )
    )
   )
  )
 )
)

## Footnotes

[branch]: /timeline?r=markdown-footnotes&nowiki

[^ 1]:  Footnotes is a Fossil' extention of
        Markdown. Your other tools may have limited support for these.
175
176
177
178
179
180
181







           .at.the.2nd.line.of.labeled.footnote.definition:
           

[^nostyle]:
  .unused.classes:
  In that case text of the footnote just looks like as if
  no special processing occured.














>
>
>
>
>
>
>
219
220
221
222
223
224
225
226
227
228
229
230
231
232
           .at.the.2nd.line.of.labeled.footnote.definition:
           

[^nostyle]:
  .unused.classes:
  In that case text of the footnote just looks like as if
  no special processing occured.


[^ <script>alert("You have been pwned!");</script> ]: Labels are escaped

[^ <textarea>"Last words here...' ]:
  <textarea>Content is also escaped</textarea>