Fossil SCM
Fix another use-after-realloc bug in handling of inline footnotes which was discovered during fuzzing. Also fix a few other issues revealed via fuzzer.
Commit
c5456211f4652561b6166ee55d7839197b94438d5bd083159cb723056c1986e3
Parent
940779668fdd569…
2 files changed
+67
-65
+3
-3
+67
-65
| --- src/markdown.c | ||
| +++ src/markdown.c | ||
| @@ -1049,11 +1049,11 @@ | ||
| 1049 | 1049 | link_e--; |
| 1050 | 1050 | } |
| 1051 | 1051 | |
| 1052 | 1052 | /* remove optional angle brackets around the link */ |
| 1053 | 1053 | if( data[link_b]=='<' ) link_b += 1; |
| 1054 | - if( data[link_e-1]=='>' ) link_e -= 1; | |
| 1054 | + if( data[link_e-1]=='>' ) link_e -= 1; /* TODO: handle link_e == 0 */ | |
| 1055 | 1055 | |
| 1056 | 1056 | /* escape backslashed character from link */ |
| 1057 | 1057 | blob_reset(link); |
| 1058 | 1058 | i = link_b; |
| 1059 | 1059 | while( i<link_e ){ |
| @@ -1081,17 +1081,18 @@ | ||
| 1081 | 1081 | struct Blob *title, |
| 1082 | 1082 | char *data, |
| 1083 | 1083 | size_t size |
| 1084 | 1084 | ){ |
| 1085 | 1085 | struct link_ref *lr; |
| 1086 | + const size_t sz = blob_size(&rndr->refs); | |
| 1086 | 1087 | |
| 1087 | 1088 | /* find the link from its id (stored temporarily in link) */ |
| 1088 | 1089 | blob_reset(link); |
| 1089 | - if( build_ref_id(link, data, size)<0 ) return -1; | |
| 1090 | + if( !sz || build_ref_id(link, data, size)<0 ) return -1; | |
| 1090 | 1091 | lr = bsearch(link, |
| 1091 | 1092 | blob_buffer(&rndr->refs), |
| 1092 | - blob_size(&rndr->refs)/sizeof(struct link_ref), | |
| 1093 | + sz/sizeof(struct link_ref), | |
| 1093 | 1094 | sizeof (struct link_ref), |
| 1094 | 1095 | cmp_link_ref); |
| 1095 | 1096 | if( !lr ) return -1; |
| 1096 | 1097 | |
| 1097 | 1098 | /* fill the output buffers */ |
| @@ -1102,20 +1103,23 @@ | ||
| 1102 | 1103 | return 0; |
| 1103 | 1104 | } |
| 1104 | 1105 | |
| 1105 | 1106 | /* |
| 1106 | 1107 | ** get_footnote() -- find a footnote by label, invoked during the 2nd pass. |
| 1107 | -** On success returns a footnote (after incrementing its nUsed field), | |
| 1108 | -** otherwise returns NULL. | |
| 1108 | +** If found then return a shallow copy of the corresponding footnote; | |
| 1109 | +** otherwise return a shallow copy of rndr->notes.misref. | |
| 1110 | +** In both cases corresponding `nUsed` field is incremented before return. | |
| 1109 | 1111 | */ |
| 1110 | -static const struct footnote* get_footnote( | |
| 1112 | +static struct footnote get_footnote( | |
| 1111 | 1113 | struct render *rndr, |
| 1112 | 1114 | const char *data, |
| 1113 | 1115 | size_t size |
| 1114 | 1116 | ){ |
| 1115 | - struct footnote *fn = NULL; | |
| 1116 | - struct Blob *id = new_work_buffer(rndr); | |
| 1117 | + struct footnote *fn = 0; | |
| 1118 | + struct Blob *id; | |
| 1119 | + if( !rndr->notes.nLbled ) goto fallback; | |
| 1120 | + id = new_work_buffer(rndr); | |
| 1117 | 1121 | if( build_ref_id(id, data, size)<0 ) goto cleanup; |
| 1118 | 1122 | fn = bsearch(id, blob_buffer(&rndr->notes.all), |
| 1119 | 1123 | rndr->notes.nLbled, |
| 1120 | 1124 | sizeof (struct footnote), |
| 1121 | 1125 | cmp_link_ref); |
| @@ -1123,16 +1127,18 @@ | ||
| 1123 | 1127 | |
| 1124 | 1128 | if( fn->nUsed == 0 ){ /* the first reference to the footnote */ |
| 1125 | 1129 | assert( fn->iMark == 0 ); |
| 1126 | 1130 | fn->iMark = ++(rndr->notes.nMarks); |
| 1127 | 1131 | } |
| 1128 | - fn->nUsed++; | |
| 1129 | 1132 | assert( fn->iMark > 0 ); |
| 1130 | - assert( fn->nUsed > 0 ); | |
| 1131 | 1133 | cleanup: |
| 1132 | 1134 | release_work_buffer( rndr, id ); |
| 1133 | - return fn; | |
| 1135 | +fallback: | |
| 1136 | + if( !fn ) fn = &rndr->notes.misref; | |
| 1137 | + fn->nUsed++; | |
| 1138 | + assert( fn->nUsed > 0 ); | |
| 1139 | + return *fn; | |
| 1134 | 1140 | } |
| 1135 | 1141 | |
| 1136 | 1142 | /* |
| 1137 | 1143 | ** Counts characters in the blank prefix within at most nHalfLines. |
| 1138 | 1144 | ** A sequence of spaces and tabs counts as odd halfline, |
| @@ -1301,11 +1307,11 @@ | ||
| 1301 | 1307 | const int is_img = (offset && data[-1] == '!'); |
| 1302 | 1308 | size_t i = 1, txt_e; |
| 1303 | 1309 | struct Blob *content = 0; |
| 1304 | 1310 | struct Blob *link = 0; |
| 1305 | 1311 | struct Blob *title = 0; |
| 1306 | - const struct footnote *fn = 0; | |
| 1312 | + struct footnote fn; | |
| 1307 | 1313 | int ret; |
| 1308 | 1314 | |
| 1309 | 1315 | /* checking whether the correct renderer exists */ |
| 1310 | 1316 | if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){ |
| 1311 | 1317 | return 0; |
| @@ -1314,18 +1320,15 @@ | ||
| 1314 | 1320 | /* looking for the matching closing bracket */ |
| 1315 | 1321 | txt_e = matching_bracket_offset(data, data+size); |
| 1316 | 1322 | if( !txt_e ) return 0; |
| 1317 | 1323 | i = txt_e + 1; |
| 1318 | 1324 | ret = 0; /* error if we don't get to the callback */ |
| 1325 | + fn.nUsed = 0; | |
| 1319 | 1326 | |
| 1320 | 1327 | /* free-standing footnote refernece */ |
| 1321 | 1328 | if(!is_img && size>3 && data[1]=='^'){ |
| 1322 | 1329 | fn = get_footnote(rndr, data+2, txt_e-2); |
| 1323 | - if( !fn ) { | |
| 1324 | - rndr->notes.misref.nUsed++; | |
| 1325 | - fn = &rndr->notes.misref; | |
| 1326 | - } | |
| 1327 | 1330 | }else{ |
| 1328 | 1331 | |
| 1329 | 1332 | /* skip "inter-bracket-whitespace" - any amount of whitespace or newline */ |
| 1330 | 1333 | /* (this is much more lax than original markdown syntax) */ |
| 1331 | 1334 | while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; } |
| @@ -1338,12 +1341,14 @@ | ||
| 1338 | 1341 | if( i<size && data[i]=='(' ){ |
| 1339 | 1342 | |
| 1340 | 1343 | if( i+2<size && data[i+1]=='^' ){ /* span-bounded inline footnote */ |
| 1341 | 1344 | |
| 1342 | 1345 | const size_t k = matching_bracket_offset(data+i, data+size); |
| 1346 | + const struct footnote *x; | |
| 1343 | 1347 | if( !k ) goto char_link_cleanup; |
| 1344 | - fn = add_inline_footnote(rndr, data+(i+2), k-2); | |
| 1348 | + x = add_inline_footnote(rndr, data+(i+2), k-2); | |
| 1349 | + if( x ) fn = *x; | |
| 1345 | 1350 | i += k+1; |
| 1346 | 1351 | }else{ /* inline style link */ |
| 1347 | 1352 | size_t span_end = i; |
| 1348 | 1353 | while( span_end<size |
| 1349 | 1354 | && !(data[span_end]==')' |
| @@ -1378,14 +1383,10 @@ | ||
| 1378 | 1383 | id_size--; |
| 1379 | 1384 | } |
| 1380 | 1385 | } |
| 1381 | 1386 | if( bFootnote ){ |
| 1382 | 1387 | fn = get_footnote(rndr, id_data, id_size); |
| 1383 | - if( !fn ) { | |
| 1384 | - rndr->notes.misref.nUsed++; | |
| 1385 | - fn = &rndr->notes.misref; | |
| 1386 | - } | |
| 1387 | 1388 | }else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){ |
| 1388 | 1389 | goto char_link_cleanup; |
| 1389 | 1390 | } |
| 1390 | 1391 | i = id_end+1; |
| 1391 | 1392 | /* shortcut reference style link */ |
| @@ -1407,14 +1408,14 @@ | ||
| 1407 | 1408 | if( is_img ){ |
| 1408 | 1409 | if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ){ |
| 1409 | 1410 | ob->nUsed--; |
| 1410 | 1411 | } |
| 1411 | 1412 | ret = rndr->make.image(ob, link, title, content, rndr->make.opaque); |
| 1412 | - }else if( fn ){ | |
| 1413 | + }else if( fn.nUsed ){ | |
| 1413 | 1414 | if( rndr->make.footnote_ref ){ |
| 1414 | - ret = rndr->make.footnote_ref(ob, content, &fn->upc, fn->iMark, | |
| 1415 | - fn->nUsed, rndr->make.opaque); | |
| 1415 | + ret = rndr->make.footnote_ref(ob, content, &fn.upc, fn.iMark, | |
| 1416 | + fn.nUsed, rndr->make.opaque); | |
| 1416 | 1417 | } |
| 1417 | 1418 | }else{ |
| 1418 | 1419 | ret = rndr->make.link(ob, link, title, content, rndr->make.opaque); |
| 1419 | 1420 | } |
| 1420 | 1421 | |
| @@ -2317,11 +2318,11 @@ | ||
| 2317 | 2318 | size_t beg, end, i; |
| 2318 | 2319 | char *txt_data; |
| 2319 | 2320 | int has_table = (rndr->make.table |
| 2320 | 2321 | && rndr->make.table_row |
| 2321 | 2322 | && rndr->make.table_cell |
| 2322 | - && memchr(data, '|', size)!=0); | |
| 2323 | + && memchr(data, '|', size)!=0); /* TODO: handle data == 0 */ | |
| 2323 | 2324 | |
| 2324 | 2325 | beg = 0; |
| 2325 | 2326 | while( beg<size ){ |
| 2326 | 2327 | txt_data = data+beg; |
| 2327 | 2328 | end = size-beg; |
| @@ -2765,50 +2766,52 @@ | ||
| 2765 | 2766 | |
| 2766 | 2767 | /* second pass: actual rendering */ |
| 2767 | 2768 | if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque); |
| 2768 | 2769 | parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text)); |
| 2769 | 2770 | |
| 2770 | - if( (blob_size(allNotes) || rndr.notes.misref.nUsed) ){ | |
| 2771 | + if( blob_size(allNotes) || rndr.notes.misref.nUsed ){ | |
| 2771 | 2772 | |
| 2772 | 2773 | /* Footnotes must be parsed for the correct discovery of (back)links */ |
| 2773 | 2774 | Blob *notes = new_work_buffer( &rndr ); |
| 2774 | - Blob *tmp = new_work_buffer( &rndr ); | |
| 2775 | - int nMarks = -1, maxDepth = 5; | |
| 2776 | - | |
| 2777 | - /* inline notes may get appended to rndr.notes.all while rendering */ | |
| 2778 | - while(1){ | |
| 2779 | - struct footnote *aNotes; | |
| 2780 | - const int N = COUNT_FOOTNOTES( allNotes ); | |
| 2781 | - | |
| 2782 | - /* make a shallow copy of `allNotes` */ | |
| 2783 | - blob_truncate(notes,0); | |
| 2784 | - blob_appendb(notes, allNotes); | |
| 2785 | - aNotes = CAST_AS_FOOTNOTES(notes); | |
| 2786 | - qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort); | |
| 2787 | - | |
| 2788 | - if( --maxDepth < 0 || nMarks == rndr.notes.nMarks ) break; | |
| 2789 | - nMarks = rndr.notes.nMarks; | |
| 2790 | - | |
| 2791 | - for(i=0; i<N; i++){ | |
| 2792 | - const int j = aNotes[i].index; | |
| 2793 | - struct footnote *x = CAST_AS_FOOTNOTES(allNotes) + j; | |
| 2794 | - assert( 0<=j && j<N ); | |
| 2795 | - if( x->bRndred || !x->nUsed ) continue; | |
| 2796 | - assert( x->iMark > 0 ); | |
| 2797 | - assert( blob_size(&x->text) ); | |
| 2798 | - blob_truncate(tmp,0); | |
| 2799 | - | |
| 2800 | - /* `allNotes` may be altered and extended through this call */ | |
| 2801 | - parse_inline(tmp, &rndr, blob_buffer(&x->text), blob_size(&x->text)); | |
| 2802 | - | |
| 2803 | - x = CAST_AS_FOOTNOTES(allNotes) + j; | |
| 2804 | - blob_truncate(&x->text,0); | |
| 2805 | - blob_appendb(&x->text, tmp); | |
| 2806 | - x->bRndred = 1; | |
| 2807 | - } | |
| 2808 | - } | |
| 2809 | - release_work_buffer(&rndr,tmp); | |
| 2775 | + if( blob_size(allNotes) ){ | |
| 2776 | + Blob *tmp = new_work_buffer( &rndr ); | |
| 2777 | + int nMarks = -1, maxDepth = 5; | |
| 2778 | + | |
| 2779 | + /* inline notes may get appended to rndr.notes.all while rendering */ | |
| 2780 | + while(1){ | |
| 2781 | + struct footnote *aNotes; | |
| 2782 | + const int N = COUNT_FOOTNOTES( allNotes ); | |
| 2783 | + | |
| 2784 | + /* make a shallow copy of `allNotes` */ | |
| 2785 | + blob_truncate(notes,0); | |
| 2786 | + blob_appendb(notes, allNotes); | |
| 2787 | + aNotes = CAST_AS_FOOTNOTES(notes); | |
| 2788 | + qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort); | |
| 2789 | + | |
| 2790 | + if( --maxDepth < 0 || nMarks == rndr.notes.nMarks ) break; | |
| 2791 | + nMarks = rndr.notes.nMarks; | |
| 2792 | + | |
| 2793 | + for(i=0; i<N; i++){ | |
| 2794 | + const int j = aNotes[i].index; | |
| 2795 | + struct footnote *x = CAST_AS_FOOTNOTES(allNotes) + j; | |
| 2796 | + assert( 0<=j && j<N ); | |
| 2797 | + if( x->bRndred || !x->nUsed ) continue; | |
| 2798 | + assert( x->iMark > 0 ); | |
| 2799 | + assert( blob_size(&x->text) ); | |
| 2800 | + blob_truncate(tmp,0); | |
| 2801 | + | |
| 2802 | + /* `allNotes` may be altered and extended through this call */ | |
| 2803 | + parse_inline(tmp, &rndr, blob_buffer(&x->text), blob_size(&x->text)); | |
| 2804 | + | |
| 2805 | + x = CAST_AS_FOOTNOTES(allNotes) + j; | |
| 2806 | + blob_truncate(&x->text,0); | |
| 2807 | + blob_appendb(&x->text, tmp); | |
| 2808 | + x->bRndred = 1; | |
| 2809 | + } | |
| 2810 | + } | |
| 2811 | + release_work_buffer(&rndr,tmp); | |
| 2812 | + } | |
| 2810 | 2813 | |
| 2811 | 2814 | /* footnotes rendering */ |
| 2812 | 2815 | if( rndr.make.footnote_item && rndr.make.footnotes ){ |
| 2813 | 2816 | Blob *all_items = new_work_buffer(&rndr); |
| 2814 | 2817 | int j = -1; |
| @@ -2815,13 +2818,12 @@ | ||
| 2815 | 2818 | |
| 2816 | 2819 | /* Assert that the in-memory layout of id, text and upc within |
| 2817 | 2820 | ** footnote struct matches the expectations of html_footnote_item() |
| 2818 | 2821 | ** If it doesn't then a compiler has done something very weird. |
| 2819 | 2822 | */ |
| 2820 | - const struct footnote *dummy = 0; | |
| 2821 | - assert( &(dummy->id) == &(dummy->text) - 1 ); | |
| 2822 | - assert( &(dummy->upc) == &(dummy->text) + 1 ); | |
| 2823 | + assert( &(rndr.notes.misref.id) == &(rndr.notes.misref.text) - 1 ); | |
| 2824 | + assert( &(rndr.notes.misref.upc) == &(rndr.notes.misref.text) + 1 ); | |
| 2823 | 2825 | |
| 2824 | 2826 | for(i=0; i<COUNT_FOOTNOTES(notes); i++){ |
| 2825 | 2827 | const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i; |
| 2826 | 2828 | const int xUsed = x->bRndred ? x->nUsed : 0; |
| 2827 | 2829 | if( !x->iMark ) break; |
| 2828 | 2830 |
| --- src/markdown.c | |
| +++ src/markdown.c | |
| @@ -1049,11 +1049,11 @@ | |
| 1049 | link_e--; |
| 1050 | } |
| 1051 | |
| 1052 | /* remove optional angle brackets around the link */ |
| 1053 | if( data[link_b]=='<' ) link_b += 1; |
| 1054 | if( data[link_e-1]=='>' ) link_e -= 1; |
| 1055 | |
| 1056 | /* escape backslashed character from link */ |
| 1057 | blob_reset(link); |
| 1058 | i = link_b; |
| 1059 | while( i<link_e ){ |
| @@ -1081,17 +1081,18 @@ | |
| 1081 | struct Blob *title, |
| 1082 | char *data, |
| 1083 | size_t size |
| 1084 | ){ |
| 1085 | struct link_ref *lr; |
| 1086 | |
| 1087 | /* find the link from its id (stored temporarily in link) */ |
| 1088 | blob_reset(link); |
| 1089 | if( build_ref_id(link, data, size)<0 ) return -1; |
| 1090 | lr = bsearch(link, |
| 1091 | blob_buffer(&rndr->refs), |
| 1092 | blob_size(&rndr->refs)/sizeof(struct link_ref), |
| 1093 | sizeof (struct link_ref), |
| 1094 | cmp_link_ref); |
| 1095 | if( !lr ) return -1; |
| 1096 | |
| 1097 | /* fill the output buffers */ |
| @@ -1102,20 +1103,23 @@ | |
| 1102 | return 0; |
| 1103 | } |
| 1104 | |
| 1105 | /* |
| 1106 | ** get_footnote() -- find a footnote by label, invoked during the 2nd pass. |
| 1107 | ** On success returns a footnote (after incrementing its nUsed field), |
| 1108 | ** otherwise returns NULL. |
| 1109 | */ |
| 1110 | static const struct footnote* get_footnote( |
| 1111 | struct render *rndr, |
| 1112 | const char *data, |
| 1113 | size_t size |
| 1114 | ){ |
| 1115 | struct footnote *fn = NULL; |
| 1116 | struct Blob *id = new_work_buffer(rndr); |
| 1117 | if( build_ref_id(id, data, size)<0 ) goto cleanup; |
| 1118 | fn = bsearch(id, blob_buffer(&rndr->notes.all), |
| 1119 | rndr->notes.nLbled, |
| 1120 | sizeof (struct footnote), |
| 1121 | cmp_link_ref); |
| @@ -1123,16 +1127,18 @@ | |
| 1123 | |
| 1124 | if( fn->nUsed == 0 ){ /* the first reference to the footnote */ |
| 1125 | assert( fn->iMark == 0 ); |
| 1126 | fn->iMark = ++(rndr->notes.nMarks); |
| 1127 | } |
| 1128 | fn->nUsed++; |
| 1129 | assert( fn->iMark > 0 ); |
| 1130 | assert( fn->nUsed > 0 ); |
| 1131 | cleanup: |
| 1132 | release_work_buffer( rndr, id ); |
| 1133 | return fn; |
| 1134 | } |
| 1135 | |
| 1136 | /* |
| 1137 | ** Counts characters in the blank prefix within at most nHalfLines. |
| 1138 | ** A sequence of spaces and tabs counts as odd halfline, |
| @@ -1301,11 +1307,11 @@ | |
| 1301 | const int is_img = (offset && data[-1] == '!'); |
| 1302 | size_t i = 1, txt_e; |
| 1303 | struct Blob *content = 0; |
| 1304 | struct Blob *link = 0; |
| 1305 | struct Blob *title = 0; |
| 1306 | const struct footnote *fn = 0; |
| 1307 | int ret; |
| 1308 | |
| 1309 | /* checking whether the correct renderer exists */ |
| 1310 | if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){ |
| 1311 | return 0; |
| @@ -1314,18 +1320,15 @@ | |
| 1314 | /* looking for the matching closing bracket */ |
| 1315 | txt_e = matching_bracket_offset(data, data+size); |
| 1316 | if( !txt_e ) return 0; |
| 1317 | i = txt_e + 1; |
| 1318 | ret = 0; /* error if we don't get to the callback */ |
| 1319 | |
| 1320 | /* free-standing footnote refernece */ |
| 1321 | if(!is_img && size>3 && data[1]=='^'){ |
| 1322 | fn = get_footnote(rndr, data+2, txt_e-2); |
| 1323 | if( !fn ) { |
| 1324 | rndr->notes.misref.nUsed++; |
| 1325 | fn = &rndr->notes.misref; |
| 1326 | } |
| 1327 | }else{ |
| 1328 | |
| 1329 | /* skip "inter-bracket-whitespace" - any amount of whitespace or newline */ |
| 1330 | /* (this is much more lax than original markdown syntax) */ |
| 1331 | while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; } |
| @@ -1338,12 +1341,14 @@ | |
| 1338 | if( i<size && data[i]=='(' ){ |
| 1339 | |
| 1340 | if( i+2<size && data[i+1]=='^' ){ /* span-bounded inline footnote */ |
| 1341 | |
| 1342 | const size_t k = matching_bracket_offset(data+i, data+size); |
| 1343 | if( !k ) goto char_link_cleanup; |
| 1344 | fn = add_inline_footnote(rndr, data+(i+2), k-2); |
| 1345 | i += k+1; |
| 1346 | }else{ /* inline style link */ |
| 1347 | size_t span_end = i; |
| 1348 | while( span_end<size |
| 1349 | && !(data[span_end]==')' |
| @@ -1378,14 +1383,10 @@ | |
| 1378 | id_size--; |
| 1379 | } |
| 1380 | } |
| 1381 | if( bFootnote ){ |
| 1382 | fn = get_footnote(rndr, id_data, id_size); |
| 1383 | if( !fn ) { |
| 1384 | rndr->notes.misref.nUsed++; |
| 1385 | fn = &rndr->notes.misref; |
| 1386 | } |
| 1387 | }else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){ |
| 1388 | goto char_link_cleanup; |
| 1389 | } |
| 1390 | i = id_end+1; |
| 1391 | /* shortcut reference style link */ |
| @@ -1407,14 +1408,14 @@ | |
| 1407 | if( is_img ){ |
| 1408 | if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ){ |
| 1409 | ob->nUsed--; |
| 1410 | } |
| 1411 | ret = rndr->make.image(ob, link, title, content, rndr->make.opaque); |
| 1412 | }else if( fn ){ |
| 1413 | if( rndr->make.footnote_ref ){ |
| 1414 | ret = rndr->make.footnote_ref(ob, content, &fn->upc, fn->iMark, |
| 1415 | fn->nUsed, rndr->make.opaque); |
| 1416 | } |
| 1417 | }else{ |
| 1418 | ret = rndr->make.link(ob, link, title, content, rndr->make.opaque); |
| 1419 | } |
| 1420 | |
| @@ -2317,11 +2318,11 @@ | |
| 2317 | size_t beg, end, i; |
| 2318 | char *txt_data; |
| 2319 | int has_table = (rndr->make.table |
| 2320 | && rndr->make.table_row |
| 2321 | && rndr->make.table_cell |
| 2322 | && memchr(data, '|', size)!=0); |
| 2323 | |
| 2324 | beg = 0; |
| 2325 | while( beg<size ){ |
| 2326 | txt_data = data+beg; |
| 2327 | end = size-beg; |
| @@ -2765,50 +2766,52 @@ | |
| 2765 | |
| 2766 | /* second pass: actual rendering */ |
| 2767 | if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque); |
| 2768 | parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text)); |
| 2769 | |
| 2770 | if( (blob_size(allNotes) || rndr.notes.misref.nUsed) ){ |
| 2771 | |
| 2772 | /* Footnotes must be parsed for the correct discovery of (back)links */ |
| 2773 | Blob *notes = new_work_buffer( &rndr ); |
| 2774 | Blob *tmp = new_work_buffer( &rndr ); |
| 2775 | int nMarks = -1, maxDepth = 5; |
| 2776 | |
| 2777 | /* inline notes may get appended to rndr.notes.all while rendering */ |
| 2778 | while(1){ |
| 2779 | struct footnote *aNotes; |
| 2780 | const int N = COUNT_FOOTNOTES( allNotes ); |
| 2781 | |
| 2782 | /* make a shallow copy of `allNotes` */ |
| 2783 | blob_truncate(notes,0); |
| 2784 | blob_appendb(notes, allNotes); |
| 2785 | aNotes = CAST_AS_FOOTNOTES(notes); |
| 2786 | qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort); |
| 2787 | |
| 2788 | if( --maxDepth < 0 || nMarks == rndr.notes.nMarks ) break; |
| 2789 | nMarks = rndr.notes.nMarks; |
| 2790 | |
| 2791 | for(i=0; i<N; i++){ |
| 2792 | const int j = aNotes[i].index; |
| 2793 | struct footnote *x = CAST_AS_FOOTNOTES(allNotes) + j; |
| 2794 | assert( 0<=j && j<N ); |
| 2795 | if( x->bRndred || !x->nUsed ) continue; |
| 2796 | assert( x->iMark > 0 ); |
| 2797 | assert( blob_size(&x->text) ); |
| 2798 | blob_truncate(tmp,0); |
| 2799 | |
| 2800 | /* `allNotes` may be altered and extended through this call */ |
| 2801 | parse_inline(tmp, &rndr, blob_buffer(&x->text), blob_size(&x->text)); |
| 2802 | |
| 2803 | x = CAST_AS_FOOTNOTES(allNotes) + j; |
| 2804 | blob_truncate(&x->text,0); |
| 2805 | blob_appendb(&x->text, tmp); |
| 2806 | x->bRndred = 1; |
| 2807 | } |
| 2808 | } |
| 2809 | release_work_buffer(&rndr,tmp); |
| 2810 | |
| 2811 | /* footnotes rendering */ |
| 2812 | if( rndr.make.footnote_item && rndr.make.footnotes ){ |
| 2813 | Blob *all_items = new_work_buffer(&rndr); |
| 2814 | int j = -1; |
| @@ -2815,13 +2818,12 @@ | |
| 2815 | |
| 2816 | /* Assert that the in-memory layout of id, text and upc within |
| 2817 | ** footnote struct matches the expectations of html_footnote_item() |
| 2818 | ** If it doesn't then a compiler has done something very weird. |
| 2819 | */ |
| 2820 | const struct footnote *dummy = 0; |
| 2821 | assert( &(dummy->id) == &(dummy->text) - 1 ); |
| 2822 | assert( &(dummy->upc) == &(dummy->text) + 1 ); |
| 2823 | |
| 2824 | for(i=0; i<COUNT_FOOTNOTES(notes); i++){ |
| 2825 | const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i; |
| 2826 | const int xUsed = x->bRndred ? x->nUsed : 0; |
| 2827 | if( !x->iMark ) break; |
| 2828 |
| --- src/markdown.c | |
| +++ src/markdown.c | |
| @@ -1049,11 +1049,11 @@ | |
| 1049 | link_e--; |
| 1050 | } |
| 1051 | |
| 1052 | /* remove optional angle brackets around the link */ |
| 1053 | if( data[link_b]=='<' ) link_b += 1; |
| 1054 | if( data[link_e-1]=='>' ) link_e -= 1; /* TODO: handle link_e == 0 */ |
| 1055 | |
| 1056 | /* escape backslashed character from link */ |
| 1057 | blob_reset(link); |
| 1058 | i = link_b; |
| 1059 | while( i<link_e ){ |
| @@ -1081,17 +1081,18 @@ | |
| 1081 | struct Blob *title, |
| 1082 | char *data, |
| 1083 | size_t size |
| 1084 | ){ |
| 1085 | struct link_ref *lr; |
| 1086 | const size_t sz = blob_size(&rndr->refs); |
| 1087 | |
| 1088 | /* find the link from its id (stored temporarily in link) */ |
| 1089 | blob_reset(link); |
| 1090 | if( !sz || build_ref_id(link, data, size)<0 ) return -1; |
| 1091 | lr = bsearch(link, |
| 1092 | blob_buffer(&rndr->refs), |
| 1093 | sz/sizeof(struct link_ref), |
| 1094 | sizeof (struct link_ref), |
| 1095 | cmp_link_ref); |
| 1096 | if( !lr ) return -1; |
| 1097 | |
| 1098 | /* fill the output buffers */ |
| @@ -1102,20 +1103,23 @@ | |
| 1103 | return 0; |
| 1104 | } |
| 1105 | |
| 1106 | /* |
| 1107 | ** get_footnote() -- find a footnote by label, invoked during the 2nd pass. |
| 1108 | ** If found then return a shallow copy of the corresponding footnote; |
| 1109 | ** otherwise return a shallow copy of rndr->notes.misref. |
| 1110 | ** In both cases corresponding `nUsed` field is incremented before return. |
| 1111 | */ |
| 1112 | static struct footnote get_footnote( |
| 1113 | struct render *rndr, |
| 1114 | const char *data, |
| 1115 | size_t size |
| 1116 | ){ |
| 1117 | struct footnote *fn = 0; |
| 1118 | struct Blob *id; |
| 1119 | if( !rndr->notes.nLbled ) goto fallback; |
| 1120 | id = new_work_buffer(rndr); |
| 1121 | if( build_ref_id(id, data, size)<0 ) goto cleanup; |
| 1122 | fn = bsearch(id, blob_buffer(&rndr->notes.all), |
| 1123 | rndr->notes.nLbled, |
| 1124 | sizeof (struct footnote), |
| 1125 | cmp_link_ref); |
| @@ -1123,16 +1127,18 @@ | |
| 1127 | |
| 1128 | if( fn->nUsed == 0 ){ /* the first reference to the footnote */ |
| 1129 | assert( fn->iMark == 0 ); |
| 1130 | fn->iMark = ++(rndr->notes.nMarks); |
| 1131 | } |
| 1132 | assert( fn->iMark > 0 ); |
| 1133 | cleanup: |
| 1134 | release_work_buffer( rndr, id ); |
| 1135 | fallback: |
| 1136 | if( !fn ) fn = &rndr->notes.misref; |
| 1137 | fn->nUsed++; |
| 1138 | assert( fn->nUsed > 0 ); |
| 1139 | return *fn; |
| 1140 | } |
| 1141 | |
| 1142 | /* |
| 1143 | ** Counts characters in the blank prefix within at most nHalfLines. |
| 1144 | ** A sequence of spaces and tabs counts as odd halfline, |
| @@ -1301,11 +1307,11 @@ | |
| 1307 | const int is_img = (offset && data[-1] == '!'); |
| 1308 | size_t i = 1, txt_e; |
| 1309 | struct Blob *content = 0; |
| 1310 | struct Blob *link = 0; |
| 1311 | struct Blob *title = 0; |
| 1312 | struct footnote fn; |
| 1313 | int ret; |
| 1314 | |
| 1315 | /* checking whether the correct renderer exists */ |
| 1316 | if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){ |
| 1317 | return 0; |
| @@ -1314,18 +1320,15 @@ | |
| 1320 | /* looking for the matching closing bracket */ |
| 1321 | txt_e = matching_bracket_offset(data, data+size); |
| 1322 | if( !txt_e ) return 0; |
| 1323 | i = txt_e + 1; |
| 1324 | ret = 0; /* error if we don't get to the callback */ |
| 1325 | fn.nUsed = 0; |
| 1326 | |
| 1327 | /* free-standing footnote refernece */ |
| 1328 | if(!is_img && size>3 && data[1]=='^'){ |
| 1329 | fn = get_footnote(rndr, data+2, txt_e-2); |
| 1330 | }else{ |
| 1331 | |
| 1332 | /* skip "inter-bracket-whitespace" - any amount of whitespace or newline */ |
| 1333 | /* (this is much more lax than original markdown syntax) */ |
| 1334 | while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; } |
| @@ -1338,12 +1341,14 @@ | |
| 1341 | if( i<size && data[i]=='(' ){ |
| 1342 | |
| 1343 | if( i+2<size && data[i+1]=='^' ){ /* span-bounded inline footnote */ |
| 1344 | |
| 1345 | const size_t k = matching_bracket_offset(data+i, data+size); |
| 1346 | const struct footnote *x; |
| 1347 | if( !k ) goto char_link_cleanup; |
| 1348 | x = add_inline_footnote(rndr, data+(i+2), k-2); |
| 1349 | if( x ) fn = *x; |
| 1350 | i += k+1; |
| 1351 | }else{ /* inline style link */ |
| 1352 | size_t span_end = i; |
| 1353 | while( span_end<size |
| 1354 | && !(data[span_end]==')' |
| @@ -1378,14 +1383,10 @@ | |
| 1383 | id_size--; |
| 1384 | } |
| 1385 | } |
| 1386 | if( bFootnote ){ |
| 1387 | fn = get_footnote(rndr, id_data, id_size); |
| 1388 | }else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){ |
| 1389 | goto char_link_cleanup; |
| 1390 | } |
| 1391 | i = id_end+1; |
| 1392 | /* shortcut reference style link */ |
| @@ -1407,14 +1408,14 @@ | |
| 1408 | if( is_img ){ |
| 1409 | if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ){ |
| 1410 | ob->nUsed--; |
| 1411 | } |
| 1412 | ret = rndr->make.image(ob, link, title, content, rndr->make.opaque); |
| 1413 | }else if( fn.nUsed ){ |
| 1414 | if( rndr->make.footnote_ref ){ |
| 1415 | ret = rndr->make.footnote_ref(ob, content, &fn.upc, fn.iMark, |
| 1416 | fn.nUsed, rndr->make.opaque); |
| 1417 | } |
| 1418 | }else{ |
| 1419 | ret = rndr->make.link(ob, link, title, content, rndr->make.opaque); |
| 1420 | } |
| 1421 | |
| @@ -2317,11 +2318,11 @@ | |
| 2318 | size_t beg, end, i; |
| 2319 | char *txt_data; |
| 2320 | int has_table = (rndr->make.table |
| 2321 | && rndr->make.table_row |
| 2322 | && rndr->make.table_cell |
| 2323 | && memchr(data, '|', size)!=0); /* TODO: handle data == 0 */ |
| 2324 | |
| 2325 | beg = 0; |
| 2326 | while( beg<size ){ |
| 2327 | txt_data = data+beg; |
| 2328 | end = size-beg; |
| @@ -2765,50 +2766,52 @@ | |
| 2766 | |
| 2767 | /* second pass: actual rendering */ |
| 2768 | if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque); |
| 2769 | parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text)); |
| 2770 | |
| 2771 | if( blob_size(allNotes) || rndr.notes.misref.nUsed ){ |
| 2772 | |
| 2773 | /* Footnotes must be parsed for the correct discovery of (back)links */ |
| 2774 | Blob *notes = new_work_buffer( &rndr ); |
| 2775 | if( blob_size(allNotes) ){ |
| 2776 | Blob *tmp = new_work_buffer( &rndr ); |
| 2777 | int nMarks = -1, maxDepth = 5; |
| 2778 | |
| 2779 | /* inline notes may get appended to rndr.notes.all while rendering */ |
| 2780 | while(1){ |
| 2781 | struct footnote *aNotes; |
| 2782 | const int N = COUNT_FOOTNOTES( allNotes ); |
| 2783 | |
| 2784 | /* make a shallow copy of `allNotes` */ |
| 2785 | blob_truncate(notes,0); |
| 2786 | blob_appendb(notes, allNotes); |
| 2787 | aNotes = CAST_AS_FOOTNOTES(notes); |
| 2788 | qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort); |
| 2789 | |
| 2790 | if( --maxDepth < 0 || nMarks == rndr.notes.nMarks ) break; |
| 2791 | nMarks = rndr.notes.nMarks; |
| 2792 | |
| 2793 | for(i=0; i<N; i++){ |
| 2794 | const int j = aNotes[i].index; |
| 2795 | struct footnote *x = CAST_AS_FOOTNOTES(allNotes) + j; |
| 2796 | assert( 0<=j && j<N ); |
| 2797 | if( x->bRndred || !x->nUsed ) continue; |
| 2798 | assert( x->iMark > 0 ); |
| 2799 | assert( blob_size(&x->text) ); |
| 2800 | blob_truncate(tmp,0); |
| 2801 | |
| 2802 | /* `allNotes` may be altered and extended through this call */ |
| 2803 | parse_inline(tmp, &rndr, blob_buffer(&x->text), blob_size(&x->text)); |
| 2804 | |
| 2805 | x = CAST_AS_FOOTNOTES(allNotes) + j; |
| 2806 | blob_truncate(&x->text,0); |
| 2807 | blob_appendb(&x->text, tmp); |
| 2808 | x->bRndred = 1; |
| 2809 | } |
| 2810 | } |
| 2811 | release_work_buffer(&rndr,tmp); |
| 2812 | } |
| 2813 | |
| 2814 | /* footnotes rendering */ |
| 2815 | if( rndr.make.footnote_item && rndr.make.footnotes ){ |
| 2816 | Blob *all_items = new_work_buffer(&rndr); |
| 2817 | int j = -1; |
| @@ -2815,13 +2818,12 @@ | |
| 2818 | |
| 2819 | /* Assert that the in-memory layout of id, text and upc within |
| 2820 | ** footnote struct matches the expectations of html_footnote_item() |
| 2821 | ** If it doesn't then a compiler has done something very weird. |
| 2822 | */ |
| 2823 | assert( &(rndr.notes.misref.id) == &(rndr.notes.misref.text) - 1 ); |
| 2824 | assert( &(rndr.notes.misref.upc) == &(rndr.notes.misref.text) + 1 ); |
| 2825 | |
| 2826 | for(i=0; i<COUNT_FOOTNOTES(notes); i++){ |
| 2827 | const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i; |
| 2828 | const int xUsed = x->bRndred ? x->nUsed : 0; |
| 2829 | if( !x->iMark ) break; |
| 2830 |
+3
-3
| --- src/markdown_html.c | ||
| +++ src/markdown_html.c | ||
| @@ -451,12 +451,10 @@ | ||
| 451 | 451 | static void html_footnote_item( |
| 452 | 452 | struct Blob *ob, const struct Blob *text, int iMark, int nUsed, void *opaque |
| 453 | 453 | ){ |
| 454 | 454 | const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque; |
| 455 | 455 | const char * const unique = ctx->unique.c; |
| 456 | - /* make.footnote_item() invocations should pass args accordingly */ | |
| 457 | - const struct Blob *upc = text+1; | |
| 458 | 456 | assert( nUsed >= 0 ); |
| 459 | 457 | /* expect BUGs if the following yields compiler warnings */ |
| 460 | 458 | |
| 461 | 459 | if( iMark < 0 ){ /* misreferences */ |
| 462 | 460 | assert( iMark == -1 ); |
| @@ -482,10 +480,12 @@ | ||
| 482 | 480 | }else if( iMark > 0 ){ /* regular, joined and overnested footnotes */ |
| 483 | 481 | char pos[24]; |
| 484 | 482 | int bJoin = 0; |
| 485 | 483 | #define _joined_footnote_indicator "<ul class='fn-joined'>" |
| 486 | 484 | #define _jfi_sz (sizeof(_joined_footnote_indicator)-1) |
| 485 | + /* make.footnote_item() invocations should pass args accordingly */ | |
| 486 | + const struct Blob *upc = text+1; | |
| 487 | 487 | assert( text ); |
| 488 | 488 | assert( blob_size(text) ); |
| 489 | 489 | memset(pos,0,24); |
| 490 | 490 | sprintf(pos, "%s-%d", unique, iMark); |
| 491 | 491 | blob_appendf(ob, "<li id='footnote%s' class='", pos); |
| @@ -541,11 +541,11 @@ | ||
| 541 | 541 | } |
| 542 | 542 | #undef _joined_footnote_indicator |
| 543 | 543 | #undef _jfi_sz |
| 544 | 544 | }else{ /* a footnote was defined but wasn't referenced */ |
| 545 | 545 | /* make.footnote_item() invocations should pass args accordingly */ |
| 546 | - const struct Blob * id = text-1; | |
| 546 | + const struct Blob *id = text-1, *upc = text+1; | |
| 547 | 547 | assert( !nUsed ); |
| 548 | 548 | assert( text ); |
| 549 | 549 | assert( blob_size(text) ); |
| 550 | 550 | assert( blob_size(id) ); |
| 551 | 551 | blob_append_literal(ob,"<li class='fn-unreferenced'>\n[^ <code>"); |
| 552 | 552 |
| --- src/markdown_html.c | |
| +++ src/markdown_html.c | |
| @@ -451,12 +451,10 @@ | |
| 451 | static void html_footnote_item( |
| 452 | struct Blob *ob, const struct Blob *text, int iMark, int nUsed, void *opaque |
| 453 | ){ |
| 454 | const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque; |
| 455 | const char * const unique = ctx->unique.c; |
| 456 | /* make.footnote_item() invocations should pass args accordingly */ |
| 457 | const struct Blob *upc = text+1; |
| 458 | assert( nUsed >= 0 ); |
| 459 | /* expect BUGs if the following yields compiler warnings */ |
| 460 | |
| 461 | if( iMark < 0 ){ /* misreferences */ |
| 462 | assert( iMark == -1 ); |
| @@ -482,10 +480,12 @@ | |
| 482 | }else if( iMark > 0 ){ /* regular, joined and overnested footnotes */ |
| 483 | char pos[24]; |
| 484 | int bJoin = 0; |
| 485 | #define _joined_footnote_indicator "<ul class='fn-joined'>" |
| 486 | #define _jfi_sz (sizeof(_joined_footnote_indicator)-1) |
| 487 | assert( text ); |
| 488 | assert( blob_size(text) ); |
| 489 | memset(pos,0,24); |
| 490 | sprintf(pos, "%s-%d", unique, iMark); |
| 491 | blob_appendf(ob, "<li id='footnote%s' class='", pos); |
| @@ -541,11 +541,11 @@ | |
| 541 | } |
| 542 | #undef _joined_footnote_indicator |
| 543 | #undef _jfi_sz |
| 544 | }else{ /* a footnote was defined but wasn't referenced */ |
| 545 | /* make.footnote_item() invocations should pass args accordingly */ |
| 546 | const struct Blob * id = text-1; |
| 547 | assert( !nUsed ); |
| 548 | assert( text ); |
| 549 | assert( blob_size(text) ); |
| 550 | assert( blob_size(id) ); |
| 551 | blob_append_literal(ob,"<li class='fn-unreferenced'>\n[^ <code>"); |
| 552 |
| --- src/markdown_html.c | |
| +++ src/markdown_html.c | |
| @@ -451,12 +451,10 @@ | |
| 451 | static void html_footnote_item( |
| 452 | struct Blob *ob, const struct Blob *text, int iMark, int nUsed, void *opaque |
| 453 | ){ |
| 454 | const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque; |
| 455 | const char * const unique = ctx->unique.c; |
| 456 | assert( nUsed >= 0 ); |
| 457 | /* expect BUGs if the following yields compiler warnings */ |
| 458 | |
| 459 | if( iMark < 0 ){ /* misreferences */ |
| 460 | assert( iMark == -1 ); |
| @@ -482,10 +480,12 @@ | |
| 480 | }else if( iMark > 0 ){ /* regular, joined and overnested footnotes */ |
| 481 | char pos[24]; |
| 482 | int bJoin = 0; |
| 483 | #define _joined_footnote_indicator "<ul class='fn-joined'>" |
| 484 | #define _jfi_sz (sizeof(_joined_footnote_indicator)-1) |
| 485 | /* make.footnote_item() invocations should pass args accordingly */ |
| 486 | const struct Blob *upc = text+1; |
| 487 | assert( text ); |
| 488 | assert( blob_size(text) ); |
| 489 | memset(pos,0,24); |
| 490 | sprintf(pos, "%s-%d", unique, iMark); |
| 491 | blob_appendf(ob, "<li id='footnote%s' class='", pos); |
| @@ -541,11 +541,11 @@ | |
| 541 | } |
| 542 | #undef _joined_footnote_indicator |
| 543 | #undef _jfi_sz |
| 544 | }else{ /* a footnote was defined but wasn't referenced */ |
| 545 | /* make.footnote_item() invocations should pass args accordingly */ |
| 546 | const struct Blob *id = text-1, *upc = text+1; |
| 547 | assert( !nUsed ); |
| 548 | assert( text ); |
| 549 | assert( blob_size(text) ); |
| 550 | assert( blob_size(id) ); |
| 551 | blob_append_literal(ob,"<li class='fn-unreferenced'>\n[^ <code>"); |
| 552 |