cmark-gfm 0.2.2 → 0.2.3
raw patch · 10 files changed
+203/−41 lines, 10 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- cbits/blocks.c +13/−2
- cbits/commonmark.c +14/−4
- cbits/footnotes.c +23/−0
- cbits/footnotes.h +2/−0
- cbits/html.c +40/−19
- cbits/inlines.c +69/−11
- cbits/node.h +7/−0
- cbits/table.c +28/−2
- changelog +4/−0
- cmark-gfm.cabal +3/−3
cbits/blocks.c view
@@ -468,7 +468,6 @@ while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); if (ev_type == CMARK_EVENT_EXIT && cur->type == CMARK_NODE_FOOTNOTE_DEFINITION) {- cmark_node_unlink(cur); cmark_footnote_create(map, cur); } }@@ -485,6 +484,15 @@ if (!footnote->ix) footnote->ix = ++ix; + // store a reference to this footnote reference's footnote definition+ // this is used by renderers when generating label ids+ cur->parent_footnote_def = footnote->node;++ // keep track of a) count of how many times this footnote def has been+ // referenced, and b) which reference index this footnote ref is at.+ // this is used by renderers when generating links and backreferences.+ cur->footnote.ref_ix = ++footnote->node->footnote.def_count;+ char n[32]; snprintf(n, sizeof(n), "%d", footnote->ix); cmark_chunk_free(parser->mem, &cur->as.literal);@@ -515,13 +523,16 @@ qsort(map->sorted, map->size, sizeof(cmark_map_entry *), sort_footnote_by_ix); for (unsigned int i = 0; i < map->size; ++i) { cmark_footnote *footnote = (cmark_footnote *)map->sorted[i];- if (!footnote->ix)+ if (!footnote->ix) {+ cmark_node_unlink(footnote->node); continue;+ } cmark_node_append_child(parser->root, footnote->node); footnote->node = NULL; } } + cmark_unlink_footnotes_map(map); cmark_map_free(map); }
cbits/commonmark.c view
@@ -477,7 +477,13 @@ case CMARK_NODE_FOOTNOTE_REFERENCE: if (entering) { LIT("[^");- OUT(cmark_chunk_to_cstr(renderer->mem, &node->as.literal), false, LITERAL);++ char *footnote_label = renderer->mem->calloc(node->parent_footnote_def->as.literal.len + 1, sizeof(char));+ memmove(footnote_label, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);++ OUT(footnote_label, false, LITERAL);+ renderer->mem->free(footnote_label);+ LIT("]"); } break;@@ -486,9 +492,13 @@ if (entering) { renderer->footnote_ix += 1; LIT("[^");- char n[32];- snprintf(n, sizeof(n), "%d", renderer->footnote_ix);- OUT(n, false, LITERAL);++ char *footnote_label = renderer->mem->calloc(node->as.literal.len + 1, sizeof(char));+ memmove(footnote_label, node->as.literal.data, node->as.literal.len);++ OUT(footnote_label, false, LITERAL);+ renderer->mem->free(footnote_label);+ LIT("]:\n"); cmark_strbuf_puts(renderer->prefix, " ");
cbits/footnotes.c view
@@ -38,3 +38,26 @@ cmark_map *cmark_footnote_map_new(cmark_mem *mem) { return cmark_map_new(mem, footnote_free); }++// Before calling `cmark_map_free` on a map with `cmark_footnotes`, first+// unlink all of the footnote nodes before freeing their memory.+//+// Sometimes, two (unused) footnote nodes can end up referencing each other,+// which as they get freed up by calling `cmark_map_free` -> `footnote_free` ->+// etc, can lead to a use-after-free error.+//+// Better to `unlink` every footnote node first, setting their next, prev, and+// parent pointers to NULL, and only then walk thru & free them up.+void cmark_unlink_footnotes_map(cmark_map *map) {+ cmark_map_entry *ref;+ cmark_map_entry *next;++ ref = map->refs;+ while(ref) {+ next = ref->next;+ if (((cmark_footnote *)ref)->node) {+ cmark_node_unlink(((cmark_footnote *)ref)->node);+ }+ ref = next;+ }+}
cbits/footnotes.h view
@@ -18,6 +18,8 @@ void cmark_footnote_create(cmark_map *map, cmark_node *node); cmark_map *cmark_footnote_map_new(cmark_mem *mem); +void cmark_unlink_footnotes_map(cmark_map *map);+ #ifdef __cplusplus } #endif
cbits/html.c view
@@ -59,17 +59,31 @@ cmark_strbuf_put(html, data, (bufsize_t)len); } -static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *html) {+static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *html, cmark_node *node) { if (renderer->written_footnote_ix >= renderer->footnote_ix) return false; renderer->written_footnote_ix = renderer->footnote_ix; - cmark_strbuf_puts(html, "<a href=\"#fnref");- char n[32];- snprintf(n, sizeof(n), "%d", renderer->footnote_ix);- cmark_strbuf_puts(html, n);- cmark_strbuf_puts(html, "\" class=\"footnote-backref\">↩</a>");+ cmark_strbuf_puts(html, "<a href=\"#fnref-");+ houdini_escape_href(html, node->as.literal.data, node->as.literal.len);+ cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩</a>"); + if (node->footnote.def_count > 1)+ {+ for(int i = 2; i <= node->footnote.def_count; i++) {+ char n[32];+ snprintf(n, sizeof(n), "%d", i);++ cmark_strbuf_puts(html, " <a href=\"#fnref-");+ houdini_escape_href(html, node->as.literal.data, node->as.literal.len);+ cmark_strbuf_puts(html, "-");+ cmark_strbuf_puts(html, n);+ cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩<sup class=\"footnote-ref\">");+ cmark_strbuf_puts(html, n);+ cmark_strbuf_puts(html, "</sup></a>");+ }+ }+ return true; } @@ -273,7 +287,7 @@ } else { if (parent->type == CMARK_NODE_FOOTNOTE_DEFINITION && node->next == NULL) { cmark_strbuf_putc(html, ' ');- S_put_footnote_backref(renderer, html);+ S_put_footnote_backref(renderer, html, parent); } cmark_strbuf_puts(html, "</p>\n"); }@@ -392,16 +406,15 @@ case CMARK_NODE_FOOTNOTE_DEFINITION: if (entering) { if (renderer->footnote_ix == 0) {- cmark_strbuf_puts(html, "<section class=\"footnotes\">\n<ol>\n");+ cmark_strbuf_puts(html, "<section class=\"footnotes\" data-footnotes>\n<ol>\n"); } ++renderer->footnote_ix;- cmark_strbuf_puts(html, "<li id=\"fn");- char n[32];- snprintf(n, sizeof(n), "%d", renderer->footnote_ix);- cmark_strbuf_puts(html, n);++ cmark_strbuf_puts(html, "<li id=\"fn-");+ houdini_escape_href(html, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, "\">\n"); } else {- if (S_put_footnote_backref(renderer, html)) {+ if (S_put_footnote_backref(renderer, html, node)) { cmark_strbuf_putc(html, '\n'); } cmark_strbuf_puts(html, "</li>\n");@@ -410,12 +423,20 @@ case CMARK_NODE_FOOTNOTE_REFERENCE: if (entering) {- cmark_strbuf_puts(html, "<sup class=\"footnote-ref\"><a href=\"#fn");- cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);- cmark_strbuf_puts(html, "\" id=\"fnref");- cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);- cmark_strbuf_puts(html, "\">");- cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);+ cmark_strbuf_puts(html, "<sup class=\"footnote-ref\"><a href=\"#fn-");+ houdini_escape_href(html, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);+ cmark_strbuf_puts(html, "\" id=\"fnref-");+ houdini_escape_href(html, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);++ if (node->footnote.ref_ix > 1) {+ char n[32];+ snprintf(n, sizeof(n), "%d", node->footnote.ref_ix);+ cmark_strbuf_puts(html, "-");+ cmark_strbuf_puts(html, n);+ }++ cmark_strbuf_puts(html, "\" data-footnote-ref>");+ houdini_escape_href(html, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, "</a></sup>"); } break;
cbits/inlines.c view
@@ -1137,19 +1137,77 @@ // What if we're a footnote link? if (parser->options & CMARK_OPT_FOOTNOTES && opener->inl_text->next &&- opener->inl_text->next->type == CMARK_NODE_TEXT &&- !opener->inl_text->next->next) {+ opener->inl_text->next->type == CMARK_NODE_TEXT) {+ cmark_chunk *literal = &opener->inl_text->next->as.literal;- if (literal->len > 1 && literal->data[0] == '^') {- inl = make_simple(subj->mem, CMARK_NODE_FOOTNOTE_REFERENCE);- inl->as.literal = cmark_chunk_dup(literal, 1, literal->len - 1);- inl->start_line = inl->end_line = subj->line;- inl->start_column = opener->inl_text->start_column;- inl->end_column = subj->pos + subj->column_offset + subj->block_offset;- cmark_node_insert_before(opener->inl_text, inl);- cmark_node_free(opener->inl_text->next);- cmark_node_free(opener->inl_text);++ // look back to the opening '[', and skip ahead to the next character+ // if we're looking at a '[^' sequence, and there is other text or nodes+ // after the ^, let's call it a footnote reference.+ if ((literal->len > 0 && literal->data[0] == '^') && (literal->len > 1 || opener->inl_text->next->next)) {++ // Before we got this far, the `handle_close_bracket` function may have+ // advanced the current state beyond our footnote's actual closing+ // bracket, ie if it went looking for a `link_label`.+ // Let's just rewind the subject's position:+ subj->pos = initial_pos;++ cmark_node *fnref = make_simple(subj->mem, CMARK_NODE_FOOTNOTE_REFERENCE);++ // the start and end of the footnote ref is the opening and closing brace+ // i.e. the subject's current position, and the opener's start_column+ int fnref_end_column = subj->pos + subj->column_offset + subj->block_offset;+ int fnref_start_column = opener->inl_text->start_column;++ // any given node delineates a substring of the line being processed,+ // with the remainder of the line being pointed to thru its 'literal'+ // struct member.+ // here, we copy the literal's pointer, moving it past the '^' character+ // for a length equal to the size of footnote reference text.+ // i.e. end_col minus start_col, minus the [ and the ^ characters+ //+ // this copies the footnote reference string, even if between the+ // `opener` and the subject's current position there are other nodes+ //+ // (first, check for underflows)+ if ((fnref_start_column + 2) <= fnref_end_column) {+ fnref->as.literal = cmark_chunk_dup(literal, 1, (fnref_end_column - fnref_start_column) - 2);+ } else {+ fnref->as.literal = cmark_chunk_dup(literal, 1, 0);+ }++ fnref->start_line = fnref->end_line = subj->line;+ fnref->start_column = fnref_start_column;+ fnref->end_column = fnref_end_column;++ // we then replace the opener with this new fnref node, the net effect+ // being replacing the opening '[' text node with a `^footnote-ref]` node.+ cmark_node_insert_before(opener->inl_text, fnref);+ process_emphasis(parser, subj, opener->previous_delimiter);+ // sometimes, the footnote reference text gets parsed into multiple nodes+ // i.e. '[^example]' parsed into '[', '^exam', 'ple]'.+ // this happens for ex with the autolink extension. when the autolinker+ // finds the 'w' character, it will split the text into multiple nodes+ // in hopes of being able to match a 'www.' substring.+ //+ // because this function is called one character at a time via the+ // `parse_inlines` function, and the current subj->pos is pointing at the+ // closing ] brace, and because we copy all the text between the [ ]+ // braces, we should be able to safely ignore and delete any nodes after+ // the opener->inl_text->next.+ //+ // therefore, here we walk thru the list and free them all up+ cmark_node *next_node;+ cmark_node *current_node = opener->inl_text->next;+ while(current_node) {+ next_node = current_node->next;+ cmark_node_free(current_node);+ current_node = next_node;+ }++ cmark_node_free(opener->inl_text);+ pop_bracket(subj); return NULL; }
cbits/node.h view
@@ -77,6 +77,13 @@ cmark_syntax_extension *extension; union {+ int ref_ix;+ int def_count;+ } footnote;++ cmark_node *parent_footnote_def;++ union { cmark_chunk literal; cmark_list list; cmark_code code;
cbits/table.c view
@@ -129,6 +129,7 @@ bufsize_t cell_matched = 1, pipe_matched = 1, offset; int expect_more_cells = 1; int row_end_offset = 0;+ int int_overflow_abort = 0; row = (table_row *)parser->mem->calloc(1, sizeof(table_row)); row->n_columns = 0;@@ -161,6 +162,12 @@ ++cell->internal_offset; } + // make sure we never wrap row->n_columns+ // offset will != len and our exit will clean up as intended+ if (row->n_columns == UINT16_MAX) {+ int_overflow_abort = 1;+ break;+ } row->n_columns += 1; row->cells = cmark_llist_append(parser->mem, row->cells, cell); }@@ -194,7 +201,7 @@ } } - if (offset != len || row->n_columns == 0) {+ if (offset != len || row->n_columns == 0 || int_overflow_abort) { free_table_row(parser->mem, row); row = NULL; }@@ -241,6 +248,11 @@ marker_row = row_from_string(self, parser, input + cmark_parser_get_first_nonspace(parser), len - cmark_parser_get_first_nonspace(parser));+ // assert may be optimized out, don't rely on it for security boundaries+ if (!marker_row) {+ return parent_container;+ }+ assert(marker_row); cmark_arena_push();@@ -264,6 +276,12 @@ len - cmark_parser_get_first_nonspace(parser)); header_row = row_from_string(self, parser, (unsigned char *)parent_string, (int)strlen(parent_string));+ // row_from_string can return NULL, add additional check to ensure n_columns match+ if (!marker_row || !header_row || header_row->n_columns != marker_row->n_columns) {+ free_table_row(parser->mem, marker_row);+ free_table_row(parser->mem, header_row);+ return parent_container;+ } } if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) {@@ -281,8 +299,10 @@ parent_container->as.opaque = parser->mem->calloc(1, sizeof(node_table)); set_n_table_columns(parent_container, header_row->n_columns); + // allocate alignments based on marker_row->n_columns+ // since we populate the alignments array based on marker_row->cells uint8_t *alignments =- (uint8_t *)parser->mem->calloc(header_row->n_columns, sizeof(uint8_t));+ (uint8_t *)parser->mem->calloc(marker_row->n_columns, sizeof(uint8_t)); cmark_llist *it = marker_row->cells; for (i = 0; it; it = it->next, ++i) { node_cell *node = (node_cell *)it->data;@@ -350,6 +370,12 @@ row = row_from_string(self, parser, input + cmark_parser_get_first_nonspace(parser), len - cmark_parser_get_first_nonspace(parser));++ if (!row) {+ // clean up the dangling node+ cmark_node_free(table_row_block);+ return NULL;+ } { cmark_llist *tmp;
changelog view
@@ -1,3 +1,7 @@+cmark-gfm 0.2.3 (9 Mar 2022)++ * Pull in upstream changes.+ cmark-gfm 0.2.2 (16 Sep 2020) * Pull in upstream changes.
cmark-gfm.cabal view
@@ -1,12 +1,12 @@ name: cmark-gfm-version: 0.2.2+version: 0.2.3 synopsis: Fast, accurate GitHub Flavored Markdown parser and renderer description: This package provides Haskell bindings for <https://github.com/github/cmark-gfm libcmark-gfm>, the reference parser for <https://github.github.com/gfm/ GitHub Flavored Markdown>, a fully specified variant of Markdown. It includes sources for- libcmark-gfm (0.29.0.gfm.0) and does not require prior installation of the+ libcmark-gfm (0.29.0.gfm.3) and does not require prior installation of the C library. homepage: https://github.com/kivikakk/cmark-gfm-hs@@ -69,7 +69,7 @@ library exposed-modules: CMarkGFM build-depends: base >=4.5 && < 5.0,- text >= 1.0 && < 1.3,+ text >= 1.0 && < 2.1, bytestring if impl(ghc < 7.6) build-depends: ghc-prim >= 0.2