Skip to content

Commit

Permalink
Merge remote-tracking branch 'gh/master' into QuietMisdreavus/sync-up…
Browse files Browse the repository at this point in the history
…stream
  • Loading branch information
QuietMisdreavus committed Dec 29, 2021
2 parents 7fc530e + 766f161 commit b9c0b19
Show file tree
Hide file tree
Showing 13 changed files with 421 additions and 57 deletions.
83 changes: 83 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: CI tests

on: [push, workflow_dispatch]

jobs:
linux:

runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cmake_opts:
- '-DCMARK_SHARED=ON'
- ''
compiler:
- c: 'clang'
cpp: 'clang++'
- c: 'gcc'
cpp: 'g++'
env:
CMAKE_OPTIONS: ${{ matrix.cmake_opts }}
CC: ${{ matrix.compiler.c }}
CXX: ${{ matrix.compiler.cpp }}

steps:
- uses: actions/checkout@v1
- name: Install valgrind
run: |
sudo apt install -y valgrind
- name: Build and test
run: |
make
make test
make leakcheck
macos:

runs-on: macOS-latest
strategy:
fail-fast: false
matrix:
cmake_opts:
- '-DCMARK_SHARED=ON'
- ''
compiler:
- c: 'clang'
cpp: 'clang++'
- c: 'gcc'
cpp: 'g++'
env:
CMAKE_OPTIONS: ${{ matrix.cmake_opts }}
CC: ${{ matrix.compiler.c }}
CXX: ${{ matrix.compiler.cpp }}

steps:
- uses: actions/checkout@v1
- name: Build and test
env:
CMAKE_OPTIONS: -DCMARK_SHARED=OFF
run: |
make
make test
windows:

runs-on: windows-latest
strategy:
fail-fast: false
matrix:
cmake_opts:
- '-DCMARK_SHARED=ON'
- ''
env:
CMAKE_OPTIONS: ${{ matrix.cmake_opts }}

steps:
- uses: actions/checkout@v1
- uses: ilammy/msvc-dev-cmd@v1
- name: Build and test
run: |
chcp 65001
nmake.exe /nologo /f Makefile.nmake test
shell: cmd
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ project(cmark-gfm)
set(PROJECT_VERSION_MAJOR 0)
set(PROJECT_VERSION_MINOR 29)
set(PROJECT_VERSION_PATCH 0)
set(PROJECT_VERSION_GFM 0)
set(PROJECT_VERSION_GFM 2)
set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.gfm.${PROJECT_VERSION_GFM})

include("FindAsan.cmake")
Expand Down
15 changes: 15 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
[0.29.0.gfm.2]
* Fixed issues with footnote rendering when used with the autolinker (#121),
and when footnotes are adjacent (#139).
* We now allow footnotes to be referenced from inside a footnote definition,
we use the footnote label for the fnref href text when rendering html, and
we insert multiple backrefs when a footnote has been referenced multiple
times (#229, #230)
* We added new data- attributes to footnote html rendering to make them
easier to style (#234)

[0.29.0.gfm.1]

* Fixed denial of service bug in GFM's table extension
per https://github.com/github/cmark-gfm/security/advisories/GHSA-7gc6-9qr5-hc85

[0.29.0]

* Update spec to 0.29.
Expand Down
15 changes: 13 additions & 2 deletions src/blocks.c
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,6 @@ static void process_footnotes(cmark_parser *parser) {
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);
}
}
Expand All @@ -511,6 +510,15 @@ static void process_footnotes(cmark_parser *parser) {
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);
Expand Down Expand Up @@ -541,13 +549,16 @@ static void process_footnotes(cmark_parser *parser) {
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);
}

Expand Down
18 changes: 14 additions & 4 deletions src/commonmark.c
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
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;
Expand All @@ -497,9 +503,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
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, " ");
Expand Down
23 changes: 23 additions & 0 deletions src/footnotes.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,26 @@ void cmark_footnote_create(cmark_map *map, cmark_node *node) {
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;
}
}
59 changes: 40 additions & 19 deletions src/html.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,30 @@ static void filter_html_block(cmark_html_renderer *renderer, uint8_t *data, size
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;
}
Expand Down Expand Up @@ -273,7 +287,7 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
} 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");
}
Expand Down Expand Up @@ -405,16 +419,15 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
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");
Expand All @@ -423,12 +436,20 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,

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;
Expand Down
2 changes: 2 additions & 0 deletions src/include/footnotes.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ typedef struct cmark_footnote cmark_footnote;
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
Expand Down
7 changes: 7 additions & 0 deletions src/include/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ struct cmark_node {

cmark_syntax_extension *extension;

union {
int ref_ix;
int def_count;
} footnote;

cmark_node *parent_footnote_def;

union {
cmark_chunk literal;
cmark_list list;
Expand Down
Loading

0 comments on commit b9c0b19

Please sign in to comment.