diff --git a/CMakeLists.txt b/CMakeLists.txt
index 06dcd3da3..ec620b7e2 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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 6)
+set(PROJECT_VERSION_GFM 11)
set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.gfm.${PROJECT_VERSION_GFM})
include("FindAsan.cmake")
diff --git a/api_test/main.c b/api_test/main.c
index efed0fb60..e950451ba 100644
--- a/api_test/main.c
+++ b/api_test/main.c
@@ -1575,6 +1575,7 @@ int main() {
int retval;
test_batch_runner *runner = test_batch_runner_new();
+ cmark_enable_safety_checks(true);
version(runner);
constructor(runner);
accessors(runner);
diff --git a/changelog.txt b/changelog.txt
index 4cc5c58ec..848e71029 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,44 @@
+[0.29.0.gfm.11]
+
+ * Improved fixes for polynomial time complexity issues per
+ https://github.com/github/cmark-gfm/security/advisories/GHSA-66g8-4hjf-77xh
+ (#323, #324)
+ * Added fuzzing target for bracketed patterns (#318)
+ * Fixed bug in list numbering introduced in
+ 763587e8775350b8cb4a2aa0f4cec3685aa96e8b (#322) which caused list numbers
+ to increment by 2
+ * Fixed strict prototype clang warning (#310)
+ * Fixed regression test (#312)
+ * Added additional output formats to quadratic fuzzer (#327)
+ * Fixed buffer overflow in fuzzing harness (#326)
+
+ Note: these changes may lead to minor changes in expected output on plaintext
+ rendering of list items. Notably, blank lines may no longer delineate the start
+ of a list when rendering to plaintext due to changes in how the tight list status
+ is calculated.
+
+[0.29.0.gfm.10]
+
+ * Fixed polynomial time complexity issue per
+ https://github.com/github/cmark-gfm/security/advisories/GHSA-r8vr-c48j-fcc5
+ * Fixed polynomial time complexity issues per
+ https://github.com/github/cmark-gfm/security/advisories/GHSA-66g8-4hjf-77xh
+
+ Note: these changes remove redundant bold tag nesting which may result
+ in existing rendering tests failing, e.g. rendering "____bold____" to html
+ will no longer yield "
bold
".
+
+[0.29.0.gfm.9]
+
+ * Cleanup: Use of a private header was cleaned up (#248)
+ * Cleanup: Man page was updated (#255)
+ * Cleanup: Warnings for -Wstrict-prototypes were cleaned up (#285)
+ * Cleanup: We avoid header duplication (#289)
+
+ * We now store positioning info for url_match (#201)
+ * We now expose cmark_parent_footnote_def for non-C renderers (#254)
+ * Footnote aria-label text now reference the specific footnote backref, and we include a data-footnote-backref-idx attribute so the label can be internationalized in a downstream filter (#307)
+
[0.29.0.gfm.8]
* We restored backwards compatibility by deprecating the `cmark_init_standard_node_flags()` requirement, which is now a noop (#305)
diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt
index d5b819802..576ad0ba5 100644
--- a/extensions/CMakeLists.txt
+++ b/extensions/CMakeLists.txt
@@ -17,8 +17,6 @@ include_directories(
${PROJECT_BINARY_DIR}/src
)
-include (GenerateExportHeader)
-
include_directories(include ${CMAKE_CURRENT_BINARY_DIR})
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE} -pg")
@@ -29,6 +27,7 @@ if (CMARK_SHARED)
set_target_properties(${LIBRARY} PROPERTIES
OUTPUT_NAME "cmark-gfm-extensions"
+ DEFINE_SYMBOL "cmark-gfm"
SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.gfm.${PROJECT_VERSION_GFM}
VERSION ${PROJECT_VERSION})
@@ -38,9 +37,6 @@ if (CMARK_SHARED)
# Avoid name clash between PROGRAM and LIBRARY pdb files.
set_target_properties(${LIBRARY} PROPERTIES PDB_NAME cmark-gfm-extensions_dll)
- generate_export_header(${LIBRARY}
- BASE_NAME cmark-gfm-extensions)
-
list(APPEND CMARK_INSTALL ${LIBRARY})
target_link_libraries(${LIBRARY} libcmark-gfm)
@@ -51,6 +47,7 @@ if (CMARK_STATIC)
set_target_properties(${STATICLIBRARY} PROPERTIES
COMPILE_FLAGS "-DCMARK_GFM_STATIC_DEFINE -DCMARK_GFM_EXTENSIONS_STATIC_DEFINE"
+ DEFINE_SYMBOL "cmark-gfm"
POSITION_INDEPENDENT_CODE ON)
if (MSVC)
@@ -63,11 +60,6 @@ if (CMARK_STATIC)
VERSION ${PROJECT_VERSION})
endif(MSVC)
- if (NOT CMARK_SHARED)
- generate_export_header(${STATICLIBRARY}
- BASE_NAME cmark-gfm-extensions)
- endif()
-
list(APPEND CMARK_INSTALL ${STATICLIBRARY})
endif()
@@ -84,7 +76,6 @@ install(TARGETS ${CMARK_INSTALL}
if (CMARK_SHARED OR CMARK_STATIC)
install(FILES
${CMAKE_CURRENT_SOURCE_DIR}/include/cmark-gfm-core-extensions.h
- ${CMAKE_CURRENT_SOURCE_DIR}/include/extensions-export.h
DESTINATION include
)
diff --git a/extensions/autolink.c b/extensions/autolink.c
index f3b75a7e9..491d96c32 100644
--- a/extensions/autolink.c
+++ b/extensions/autolink.c
@@ -267,6 +267,11 @@ static cmark_node *url_match(cmark_parser *parser, cmark_node *parent,
cmark_node *text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
text->as.literal = url;
cmark_node_append_child(node, text);
+
+ node->start_line = text->start_line = node->end_line = text->end_line = cmark_inline_parser_get_line(inline_parser);
+
+ node->start_column = text->start_column = max_rewind - rewind;
+ node->end_column = text->end_column = cmark_inline_parser_get_column(inline_parser) - 1;
return node;
}
diff --git a/extensions/include/cmark-gfm-core-extensions.h b/extensions/include/cmark-gfm-core-extensions.h
index d85a89237..6f7aada65 100644
--- a/extensions/include/cmark-gfm-core-extensions.h
+++ b/extensions/include/cmark-gfm-core-extensions.h
@@ -6,40 +6,40 @@ extern "C" {
#endif
#include "cmark-gfm-extension_api.h"
-#include "extensions-export.h"
-#include "cmark-gfm_config.h" // for bool
+#include "export.h"
+#include
#include
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
void cmark_gfm_core_extensions_ensure_registered(void);
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node);
/** Sets the number of columns for the table, returning 1 on success and 0 on error.
*/
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns);
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node);
/** Sets the alignments for the table, returning 1 on success and 0 on error.
*/
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments);
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node);
/** Sets the column span for the table cell, returning 1 on success and 0 on error.
*/
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
int cmark_gfm_extensions_set_table_cell_colspan(cmark_node *node, unsigned colspan);
/** Sets the row span for the table cell, returning 1 on success and 0 on error.
*/
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
int cmark_gfm_extensions_set_table_cell_rowspan(cmark_node *node, unsigned rowspan);
/**
@@ -50,7 +50,7 @@ int cmark_gfm_extensions_set_table_cell_rowspan(cmark_node *node, unsigned rowsp
Column span is only parsed when \c CMARK_OPT_TABLE_SPANS is set.
*/
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
unsigned cmark_gfm_extensions_get_table_cell_colspan(cmark_node *node);
/**
@@ -61,22 +61,22 @@ unsigned cmark_gfm_extensions_get_table_cell_colspan(cmark_node *node);
Row span is only parsed when \c CMARK_OPT_TABLE_SPANS is set.
*/
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
unsigned cmark_gfm_extensions_get_table_cell_rowspan(cmark_node *node);
/** Sets whether the node is a table header row, returning 1 on success and 0 on error.
*/
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header);
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node);
/* For backwards compatibility */
#define cmark_gfm_extensions_tasklist_is_checked cmark_gfm_extensions_get_tasklist_item_checked
/** Sets whether a tasklist item is "checked" (completed), returning 1 on success and 0 on error.
*/
-CMARK_GFM_EXTENSIONS_EXPORT
+CMARK_GFM_EXPORT
int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked);
#ifdef __cplusplus
diff --git a/extensions/include/extensions-export.h b/extensions/include/extensions-export.h
deleted file mode 100644
index f91b8564c..000000000
--- a/extensions/include/extensions-export.h
+++ /dev/null
@@ -1,60 +0,0 @@
-#ifndef CMARK_GFM_EXTENSIONS_EXPORT_H
-#define CMARK_GFM_EXTENSIONS_EXPORT_H
-
-#ifdef CMARK_USE_CMAKE_HEADERS
-// if the CMake config header exists, use that instead of this Swift package prebuilt one
-// we need to undefine the header guard, since export.h uses the same one
-#undef CMARK_GFM_EXTENSIONS_EXPORT_H
-#include "cmark-gfm-extensions_export.h"
-#else
-
-#ifdef CMARK_GFM_EXTENSIONS_STATIC_DEFINE
-# define CMARK_GFM_EXTENSIONS_EXPORT
-# define CMARK_GFM_EXTENSIONS_NO_EXPORT
-#else
-# if defined(_WIN32)
-# ifndef CMARK_GFM_EXTENSIONS_EXPORT
-# ifdef libcmark_gfm_extensions_EXPORTS
-# define CMARK_GFM_EXTENSIONS_EXPORT __declspec(dllexport)
-# else
-# define CMARK_GFM_EXTENSIONS_EXPORT __declspec(dllimport)
-# endif
-# endif
-
-# ifndef CMARK_GFM_EXTENSIONS_NO_EXPORT
-# define CMARK_GFM_EXTENSIONS_NO_EXPORT
-# endif
-# else
-# ifndef CMARK_GFM_EXTENSIONS_EXPORT
-# ifdef libcmark_gfm_extensions_EXPORTS
-# define CMARK_GFM_EXTENSIONS_EXPORT __attribute__((__visibility__("default")))
-# else
-# define CMARK_GFM_EXTENSIONS_EXPORT __attribute__((__visibility__("default")))
-# endif
-# endif
-
-# ifndef CMARK_GFM_EXTENSIONS_NO_EXPORT
-# define CMARK_GFM_EXTENSIONS_NO_EXPORT __attribute__((__visibility__("hidden")))
-# endif
-# endif
-#endif
-
-#ifndef CMARK_GFM_EXTENSIONS_DEPRECATED
-# if defined(_WIN32)
-# define CMARK_GFM_EXTENSIONS_DEPRECATED __declspec(deprecated)
-# else
-# define CMARK_GFM_EXTENSIONS_DEPRECATED __attribute__ ((__deprecated__))
-# endif
-#endif
-
-#ifndef CMARK_GFM_EXTENSIONS_DEPRECATED_EXPORT
-# define CMARK_GFM_EXTENSIONS_DEPRECATED_EXPORT CMARK_GFM_EXTENSIONS_EXPORT CMARK_GFM_EXTENSIONS_DEPRECATED
-#endif
-
-#ifndef CMARK_GFM_EXTENSIONS_DEPRECATED_NO_EXPORT
-# define CMARK_GFM_EXTENSIONS_DEPRECATED_NO_EXPORT CMARK_GFM_EXTENSIONS_NO_EXPORT CMARK_GFM_EXTENSIONS_DEPRECATED
-#endif
-
-#endif /* CMARK_GFM_EXTENSIONS_EXPORT_H */
-
-#endif /* "cmark-gfm-extensions_export.h" */
diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt
index 3cae79421..a9ed57aa5 100644
--- a/fuzz/CMakeLists.txt
+++ b/fuzz/CMakeLists.txt
@@ -19,3 +19,4 @@ macro(fuzzer name)
endmacro()
fuzzer(fuzz_quadratic)
+fuzzer(fuzz_quadratic_brackets)
diff --git a/fuzz/fuzz_quadratic.c b/fuzz/fuzz_quadratic.c
index cab1b6330..523c0f444 100644
--- a/fuzz/fuzz_quadratic.c
+++ b/fuzz/fuzz_quadratic.c
@@ -75,8 +75,13 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
cmark_parser_feed(parser, markdown, markdown_size);
cmark_node *doc = cmark_parser_finish(parser);
-
+
free(cmark_render_html(doc, fuzz_config.options, NULL));
+ free(cmark_render_xml(doc, fuzz_config.options));
+ free(cmark_render_man(doc, fuzz_config.options, 80));
+ free(cmark_render_commonmark(doc, fuzz_config.options, 80));
+ free(cmark_render_plaintext(doc, fuzz_config.options, 80));
+ free(cmark_render_latex(doc, fuzz_config.options, 80));
cmark_node_free(doc);
cmark_parser_free(parser);
diff --git a/fuzz/fuzz_quadratic_brackets.c b/fuzz/fuzz_quadratic_brackets.c
new file mode 100644
index 000000000..5c1646464
--- /dev/null
+++ b/fuzz/fuzz_quadratic_brackets.c
@@ -0,0 +1,110 @@
+#include
+#include
+#include
+#include "cmark-gfm.h"
+#include "cmark-gfm-core-extensions.h"
+#include
+#include
+#include
+#include
+
+const char *extension_names[] = {
+ "autolink",
+ "strikethrough",
+ "table",
+ "tagfilter",
+ NULL,
+};
+
+int LLVMFuzzerInitialize(int *argc, char ***argv) {
+ cmark_gfm_core_extensions_ensure_registered();
+ return 0;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ struct __attribute__((packed)) {
+ int options;
+ int width;
+ uint8_t startlen;
+ uint8_t openlen;
+ uint8_t middlelen;
+ uint8_t closelen;
+ } fuzz_config;
+
+ if (size >= sizeof(fuzz_config)) {
+ /* The beginning of `data` is treated as fuzzer configuration */
+ memcpy(&fuzz_config, data, sizeof(fuzz_config));
+
+ /* Test options that are used by GitHub. */
+ fuzz_config.options = CMARK_OPT_UNSAFE | CMARK_OPT_FOOTNOTES | CMARK_OPT_GITHUB_PRE_LANG | CMARK_OPT_HARDBREAKS;
+ fuzz_config.openlen = fuzz_config.openlen & 0x7;
+ fuzz_config.middlelen = fuzz_config.middlelen & 0x7;
+ fuzz_config.closelen = fuzz_config.closelen & 0x7;
+
+ /* Remainder of input is the markdown */
+ const char *markdown0 = (const char *)(data + sizeof(fuzz_config));
+ const size_t markdown_size0 = size - sizeof(fuzz_config);
+ char markdown[0x80000];
+ if (markdown_size0 <= sizeof(markdown)) {
+ size_t markdown_size = 0;
+ const size_t componentslen = fuzz_config.startlen + fuzz_config.openlen + fuzz_config.middlelen + fuzz_config.closelen;
+ if (componentslen <= markdown_size0) {
+ size_t offset = 0;
+ const size_t endlen = markdown_size0 - componentslen;
+ memcpy(&markdown[markdown_size], &markdown0[offset], fuzz_config.startlen);
+ markdown_size += fuzz_config.startlen;
+ offset += fuzz_config.startlen;
+
+ if (0 < fuzz_config.openlen) {
+ while (markdown_size + fuzz_config.openlen <= sizeof(markdown)/2) {
+ memcpy(&markdown[markdown_size], &markdown0[offset],
+ fuzz_config.openlen);
+ markdown_size += fuzz_config.openlen;
+ }
+ offset += fuzz_config.openlen;
+ }
+ memcpy(&markdown[markdown_size], &markdown0[offset],
+ fuzz_config.middlelen);
+ markdown_size += fuzz_config.middlelen;
+ offset += fuzz_config.middlelen;
+ if (0 < fuzz_config.closelen) {
+ while (markdown_size + fuzz_config.closelen + endlen <= sizeof(markdown)) {
+ memcpy(&markdown[markdown_size], &markdown0[offset],
+ fuzz_config.closelen);
+ markdown_size += fuzz_config.closelen;
+ }
+ offset += fuzz_config.closelen;
+ }
+ if (markdown_size + endlen <= sizeof(markdown)) {
+ memcpy(&markdown[markdown_size], &markdown0[offset],
+ endlen);
+ markdown_size += endlen;
+ }
+ } else {
+ markdown_size = markdown_size0;
+ memcpy(markdown, markdown0, markdown_size);
+ }
+
+ cmark_parser *parser = cmark_parser_new(fuzz_config.options);
+
+ for (const char **it = extension_names; *it; ++it) {
+ const char *extension_name = *it;
+ cmark_syntax_extension *syntax_extension = cmark_find_syntax_extension(extension_name);
+ if (!syntax_extension) {
+ fprintf(stderr, "%s is not a valid syntax extension\n", extension_name);
+ abort();
+ }
+ cmark_parser_attach_syntax_extension(parser, syntax_extension);
+ }
+
+ cmark_parser_feed(parser, markdown, markdown_size);
+ cmark_node *doc = cmark_parser_finish(parser);
+
+ free(cmark_render_html(doc, fuzz_config.options, NULL));
+
+ cmark_node_free(doc);
+ cmark_parser_free(parser);
+ }
+ }
+ return 0;
+}
diff --git a/man/man1/cmark-gfm.1 b/man/man1/cmark-gfm.1
index 5c68d79c3..4fca62732 100644
--- a/man/man1/cmark-gfm.1
+++ b/man/man1/cmark-gfm.1
@@ -58,10 +58,10 @@ be rendered as curly quotes, depending on their position.
\f[C]\-\-\-\f[] will be rendered as an em-dash.
\f[C]...\f[] will be rendered as ellipses.
.TP 12n
-.B \-\-safe
-Do not render raw HTML or potentially dangerous URLs.
-(Raw HTML is replaced by a placeholder comment; potentially
-dangerous URLs are replaced by empty strings.) Dangerous
+.B \-\-unsafe
+Render raw HTML and potentially dangerous URLs.
+(Raw HTML is not replaced by a placeholder comment; potentially
+dangerous URLs are not replaced by empty strings.) Dangerous
URLs are those that begin with `javascript:`, `vbscript:`,
`file:`, or `data:` (except for `image/png`, `image/gif`,
`image/jpeg`, or `image/webp` mime types).
diff --git a/src/arena.c b/src/arena.c
index c1cea10eb..86bf5b749 100644
--- a/src/arena.c
+++ b/src/arena.c
@@ -118,6 +118,6 @@ static void arena_free(void *ptr) {
cmark_mem CMARK_ARENA_MEM_ALLOCATOR = {arena_calloc, arena_realloc, arena_free};
-cmark_mem *cmark_get_arena_mem_allocator() {
+cmark_mem *cmark_get_arena_mem_allocator(void) {
return &CMARK_ARENA_MEM_ALLOCATOR;
}
diff --git a/src/blocks.c b/src/blocks.c
index 49bcbce39..a7290652c 100644
--- a/src/blocks.c
+++ b/src/blocks.c
@@ -27,6 +27,14 @@
#define CODE_INDENT 4
#define TAB_STOP 4
+/**
+ * Very deeply nested lists can cause quadratic performance issues.
+ * This constant is used in open_new_blocks() to limit the nesting
+ * depth. It is unlikely that a non-contrived markdown document will
+ * be nested this deeply.
+ */
+#define MAX_LIST_DEPTH 100
+
#ifndef MIN
#define MIN(x, y) ((x < y) ? x : y)
#endif
@@ -1148,10 +1156,11 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
bool has_content;
int save_offset;
int save_column;
+ size_t depth = 0;
while (cont_type != CMARK_NODE_CODE_BLOCK &&
cont_type != CMARK_NODE_HTML_BLOCK) {
-
+ depth++;
S_find_first_nonspace(parser, input);
indented = parser->indent >= CODE_INDENT;
@@ -1253,6 +1262,7 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
(*container)->internal_offset = matched;
} else if ((!indented || cont_type == CMARK_NODE_LIST) &&
parser->indent < 4 &&
+ depth < MAX_LIST_DEPTH &&
(matched = parse_list_marker(
parser->mem, input, parser->first_nonspace,
(*container)->type == CMARK_NODE_PARAGRAPH, &data))) {
diff --git a/src/cmark.c b/src/cmark.c
index 08477fa57..f2619f429 100644
--- a/src/cmark.c
+++ b/src/cmark.c
@@ -10,9 +10,9 @@
cmark_node_type CMARK_NODE_LAST_BLOCK = CMARK_NODE_FOOTNOTE_DEFINITION;
cmark_node_type CMARK_NODE_LAST_INLINE = CMARK_NODE_ATTRIBUTE;
-int cmark_version() { return CMARK_GFM_VERSION; }
+int cmark_version(void) { return CMARK_GFM_VERSION; }
-const char *cmark_version_string() { return CMARK_GFM_VERSION_STRING; }
+const char *cmark_version_string(void) { return CMARK_GFM_VERSION_STRING; }
static void *xcalloc(size_t nmem, size_t size) {
void *ptr = calloc(nmem, size);
@@ -38,7 +38,7 @@ static void xfree(void *ptr) {
cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, xfree};
-cmark_mem *cmark_get_default_mem_allocator() {
+cmark_mem *cmark_get_default_mem_allocator(void) {
return &CMARK_DEFAULT_MEM_ALLOCATOR;
}
diff --git a/src/commonmark.c b/src/commonmark.c
index 328da12a3..d296567e0 100644
--- a/src/commonmark.c
+++ b/src/commonmark.c
@@ -154,23 +154,8 @@ static bool is_autolink(cmark_node *node) {
link_text->as.literal.len) == 0);
}
-// if node is a block node, returns node.
-// otherwise returns first block-level node that is an ancestor of node.
-// if there is no block-level ancestor, returns NULL.
-static cmark_node *get_containing_block(cmark_node *node) {
- while (node) {
- if (CMARK_NODE_BLOCK_P(node)) {
- return node;
- } else {
- node = node->parent;
- }
- }
- return NULL;
-}
-
static int S_render_node(cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
- cmark_node *tmp;
int list_number;
cmark_delim_type list_delim;
int numticks;
@@ -190,14 +175,17 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
// Don't adjust tight list status til we've started the list.
// Otherwise we loose the blank line between a paragraph and
// a following list.
- if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) {
- tmp = get_containing_block(node);
- renderer->in_tight_list_item =
- tmp && // tmp might be NULL if there is no containing block
- ((tmp->type == CMARK_NODE_ITEM &&
- cmark_node_get_list_tight(tmp->parent)) ||
- (tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM &&
- cmark_node_get_list_tight(tmp->parent->parent)));
+ if (entering) {
+ if (node->parent && node->parent->type == CMARK_NODE_ITEM) {
+ renderer->in_tight_list_item = node->parent->parent->as.list.tight;
+ }
+ } else {
+ if (node->type == CMARK_NODE_LIST) {
+ renderer->in_tight_list_item =
+ node->parent &&
+ node->parent->type == CMARK_NODE_ITEM &&
+ node->parent->parent->as.list.tight;
+ }
}
if (node->extension && node->extension->commonmark_render_func) {
@@ -235,13 +223,8 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) {
marker_width = 4;
} else {
- list_number = cmark_node_get_list_start(node->parent);
+ list_number = cmark_node_get_item_index(node);
list_delim = cmark_node_get_list_delim(node->parent);
- tmp = node;
- while (tmp->prev) {
- tmp = tmp->prev;
- list_number += 1;
- }
// we ensure a width of at least 4 so
// we get nice transition from single digits
// to double
@@ -406,10 +389,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
break;
case CMARK_NODE_STRONG:
- if (entering) {
- LIT("**");
- } else {
- LIT("**");
+ if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) {
+ if (entering) {
+ LIT("**");
+ } else {
+ LIT("**");
+ }
}
break;
diff --git a/src/html.c b/src/html.c
index 96daa18e2..f1f2ff774 100644
--- a/src/html.c
+++ b/src/html.c
@@ -63,10 +63,16 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *
if (renderer->written_footnote_ix >= renderer->footnote_ix)
return false;
renderer->written_footnote_ix = renderer->footnote_ix;
+ char m[32];
+ snprintf(m, sizeof(m), "%d", renderer->written_footnote_ix);
cmark_strbuf_puts(html, "as.literal.data, node->as.literal.len);
- cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩");
+ cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"");
+ cmark_strbuf_puts(html, m);
+ cmark_strbuf_puts(html, "\" aria-label=\"Back to reference ");
+ cmark_strbuf_puts(html, m);
+ cmark_strbuf_puts(html, "\">↩");
if (node->footnote.def_count > 1)
{
@@ -78,7 +84,15 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *
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\">↩