packages feed

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 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