commonmarker 0.23.0 → 0.23.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of commonmarker might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +12 -7
- data/bin/commonmarker +2 -7
- data/commonmarker.gemspec +2 -0
- data/ext/commonmarker/blocks.c +13 -2
- data/ext/commonmarker/cmark-gfm_version.h +2 -2
- data/ext/commonmarker/commonmark.c +14 -4
- data/ext/commonmarker/commonmarker.c +29 -44
- data/ext/commonmarker/ext_scanners.c +360 -640
- data/ext/commonmarker/footnotes.c +23 -0
- data/ext/commonmarker/footnotes.h +2 -0
- data/ext/commonmarker/html.c +40 -19
- data/ext/commonmarker/inlines.c +69 -11
- data/ext/commonmarker/node.h +7 -0
- data/ext/commonmarker/table.c +98 -53
- data/lib/commonmarker/config.rb +10 -5
- data/lib/commonmarker/errors.rb +12 -0
- data/lib/commonmarker/version.rb +1 -1
- data/lib/commonmarker.rb +1 -3
- data/test/benchmark.rb +25 -18
- data/test/test_basics.rb +17 -0
- data/test/test_extensions.rb +3 -0
- data/test/test_footnotes.rb +24 -12
- data/test/test_maliciousness.rb +0 -5
- data/test/test_smartpunct.rb +5 -2
- metadata +24 -22
@@ -38,3 +38,26 @@ void cmark_footnote_create(cmark_map *map, cmark_node *node) {
|
|
38
38
|
cmark_map *cmark_footnote_map_new(cmark_mem *mem) {
|
39
39
|
return cmark_map_new(mem, footnote_free);
|
40
40
|
}
|
41
|
+
|
42
|
+
// Before calling `cmark_map_free` on a map with `cmark_footnotes`, first
|
43
|
+
// unlink all of the footnote nodes before freeing their memory.
|
44
|
+
//
|
45
|
+
// Sometimes, two (unused) footnote nodes can end up referencing each other,
|
46
|
+
// which as they get freed up by calling `cmark_map_free` -> `footnote_free` ->
|
47
|
+
// etc, can lead to a use-after-free error.
|
48
|
+
//
|
49
|
+
// Better to `unlink` every footnote node first, setting their next, prev, and
|
50
|
+
// parent pointers to NULL, and only then walk thru & free them up.
|
51
|
+
void cmark_unlink_footnotes_map(cmark_map *map) {
|
52
|
+
cmark_map_entry *ref;
|
53
|
+
cmark_map_entry *next;
|
54
|
+
|
55
|
+
ref = map->refs;
|
56
|
+
while(ref) {
|
57
|
+
next = ref->next;
|
58
|
+
if (((cmark_footnote *)ref)->node) {
|
59
|
+
cmark_node_unlink(((cmark_footnote *)ref)->node);
|
60
|
+
}
|
61
|
+
ref = next;
|
62
|
+
}
|
63
|
+
}
|
data/ext/commonmarker/html.c
CHANGED
@@ -59,16 +59,30 @@ static void filter_html_block(cmark_html_renderer *renderer, uint8_t *data, size
|
|
59
59
|
cmark_strbuf_put(html, data, (bufsize_t)len);
|
60
60
|
}
|
61
61
|
|
62
|
-
static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *html) {
|
62
|
+
static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *html, cmark_node *node) {
|
63
63
|
if (renderer->written_footnote_ix >= renderer->footnote_ix)
|
64
64
|
return false;
|
65
65
|
renderer->written_footnote_ix = renderer->footnote_ix;
|
66
66
|
|
67
|
-
cmark_strbuf_puts(html, "<a href=\"#fnref");
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
cmark_strbuf_puts(html, "<a href=\"#fnref-");
|
68
|
+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
|
69
|
+
cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩</a>");
|
70
|
+
|
71
|
+
if (node->footnote.def_count > 1)
|
72
|
+
{
|
73
|
+
for(int i = 2; i <= node->footnote.def_count; i++) {
|
74
|
+
char n[32];
|
75
|
+
snprintf(n, sizeof(n), "%d", i);
|
76
|
+
|
77
|
+
cmark_strbuf_puts(html, " <a href=\"#fnref-");
|
78
|
+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
|
79
|
+
cmark_strbuf_puts(html, "-");
|
80
|
+
cmark_strbuf_puts(html, n);
|
81
|
+
cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩<sup class=\"footnote-ref\">");
|
82
|
+
cmark_strbuf_puts(html, n);
|
83
|
+
cmark_strbuf_puts(html, "</sup></a>");
|
84
|
+
}
|
85
|
+
}
|
72
86
|
|
73
87
|
return true;
|
74
88
|
}
|
@@ -273,7 +287,7 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
|
|
273
287
|
} else {
|
274
288
|
if (parent->type == CMARK_NODE_FOOTNOTE_DEFINITION && node->next == NULL) {
|
275
289
|
cmark_strbuf_putc(html, ' ');
|
276
|
-
S_put_footnote_backref(renderer, html);
|
290
|
+
S_put_footnote_backref(renderer, html, parent);
|
277
291
|
}
|
278
292
|
cmark_strbuf_puts(html, "</p>\n");
|
279
293
|
}
|
@@ -392,16 +406,15 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
|
|
392
406
|
case CMARK_NODE_FOOTNOTE_DEFINITION:
|
393
407
|
if (entering) {
|
394
408
|
if (renderer->footnote_ix == 0) {
|
395
|
-
cmark_strbuf_puts(html, "<section class=\"footnotes\">\n<ol>\n");
|
409
|
+
cmark_strbuf_puts(html, "<section class=\"footnotes\" data-footnotes>\n<ol>\n");
|
396
410
|
}
|
397
411
|
++renderer->footnote_ix;
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
cmark_strbuf_puts(html, n);
|
412
|
+
|
413
|
+
cmark_strbuf_puts(html, "<li id=\"fn-");
|
414
|
+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
|
402
415
|
cmark_strbuf_puts(html, "\">\n");
|
403
416
|
} else {
|
404
|
-
if (S_put_footnote_backref(renderer, html)) {
|
417
|
+
if (S_put_footnote_backref(renderer, html, node)) {
|
405
418
|
cmark_strbuf_putc(html, '\n');
|
406
419
|
}
|
407
420
|
cmark_strbuf_puts(html, "</li>\n");
|
@@ -410,12 +423,20 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
|
|
410
423
|
|
411
424
|
case CMARK_NODE_FOOTNOTE_REFERENCE:
|
412
425
|
if (entering) {
|
413
|
-
cmark_strbuf_puts(html, "<sup class=\"footnote-ref\"><a href=\"#fn");
|
414
|
-
|
415
|
-
cmark_strbuf_puts(html, "\" id=\"fnref");
|
416
|
-
|
417
|
-
|
418
|
-
|
426
|
+
cmark_strbuf_puts(html, "<sup class=\"footnote-ref\"><a href=\"#fn-");
|
427
|
+
houdini_escape_href(html, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);
|
428
|
+
cmark_strbuf_puts(html, "\" id=\"fnref-");
|
429
|
+
houdini_escape_href(html, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);
|
430
|
+
|
431
|
+
if (node->footnote.ref_ix > 1) {
|
432
|
+
char n[32];
|
433
|
+
snprintf(n, sizeof(n), "%d", node->footnote.ref_ix);
|
434
|
+
cmark_strbuf_puts(html, "-");
|
435
|
+
cmark_strbuf_puts(html, n);
|
436
|
+
}
|
437
|
+
|
438
|
+
cmark_strbuf_puts(html, "\" data-footnote-ref>");
|
439
|
+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
|
419
440
|
cmark_strbuf_puts(html, "</a></sup>");
|
420
441
|
}
|
421
442
|
break;
|
data/ext/commonmarker/inlines.c
CHANGED
@@ -1137,19 +1137,77 @@ noMatch:
|
|
1137
1137
|
// What if we're a footnote link?
|
1138
1138
|
if (parser->options & CMARK_OPT_FOOTNOTES &&
|
1139
1139
|
opener->inl_text->next &&
|
1140
|
-
opener->inl_text->next->type == CMARK_NODE_TEXT
|
1141
|
-
|
1140
|
+
opener->inl_text->next->type == CMARK_NODE_TEXT) {
|
1141
|
+
|
1142
1142
|
cmark_chunk *literal = &opener->inl_text->next->as.literal;
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1143
|
+
|
1144
|
+
// look back to the opening '[', and skip ahead to the next character
|
1145
|
+
// if we're looking at a '[^' sequence, and there is other text or nodes
|
1146
|
+
// after the ^, let's call it a footnote reference.
|
1147
|
+
if ((literal->len > 0 && literal->data[0] == '^') && (literal->len > 1 || opener->inl_text->next->next)) {
|
1148
|
+
|
1149
|
+
// Before we got this far, the `handle_close_bracket` function may have
|
1150
|
+
// advanced the current state beyond our footnote's actual closing
|
1151
|
+
// bracket, ie if it went looking for a `link_label`.
|
1152
|
+
// Let's just rewind the subject's position:
|
1153
|
+
subj->pos = initial_pos;
|
1154
|
+
|
1155
|
+
cmark_node *fnref = make_simple(subj->mem, CMARK_NODE_FOOTNOTE_REFERENCE);
|
1156
|
+
|
1157
|
+
// the start and end of the footnote ref is the opening and closing brace
|
1158
|
+
// i.e. the subject's current position, and the opener's start_column
|
1159
|
+
int fnref_end_column = subj->pos + subj->column_offset + subj->block_offset;
|
1160
|
+
int fnref_start_column = opener->inl_text->start_column;
|
1161
|
+
|
1162
|
+
// any given node delineates a substring of the line being processed,
|
1163
|
+
// with the remainder of the line being pointed to thru its 'literal'
|
1164
|
+
// struct member.
|
1165
|
+
// here, we copy the literal's pointer, moving it past the '^' character
|
1166
|
+
// for a length equal to the size of footnote reference text.
|
1167
|
+
// i.e. end_col minus start_col, minus the [ and the ^ characters
|
1168
|
+
//
|
1169
|
+
// this copies the footnote reference string, even if between the
|
1170
|
+
// `opener` and the subject's current position there are other nodes
|
1171
|
+
//
|
1172
|
+
// (first, check for underflows)
|
1173
|
+
if ((fnref_start_column + 2) <= fnref_end_column) {
|
1174
|
+
fnref->as.literal = cmark_chunk_dup(literal, 1, (fnref_end_column - fnref_start_column) - 2);
|
1175
|
+
} else {
|
1176
|
+
fnref->as.literal = cmark_chunk_dup(literal, 1, 0);
|
1177
|
+
}
|
1178
|
+
|
1179
|
+
fnref->start_line = fnref->end_line = subj->line;
|
1180
|
+
fnref->start_column = fnref_start_column;
|
1181
|
+
fnref->end_column = fnref_end_column;
|
1182
|
+
|
1183
|
+
// we then replace the opener with this new fnref node, the net effect
|
1184
|
+
// being replacing the opening '[' text node with a `^footnote-ref]` node.
|
1185
|
+
cmark_node_insert_before(opener->inl_text, fnref);
|
1186
|
+
|
1152
1187
|
process_emphasis(parser, subj, opener->previous_delimiter);
|
1188
|
+
// sometimes, the footnote reference text gets parsed into multiple nodes
|
1189
|
+
// i.e. '[^example]' parsed into '[', '^exam', 'ple]'.
|
1190
|
+
// this happens for ex with the autolink extension. when the autolinker
|
1191
|
+
// finds the 'w' character, it will split the text into multiple nodes
|
1192
|
+
// in hopes of being able to match a 'www.' substring.
|
1193
|
+
//
|
1194
|
+
// because this function is called one character at a time via the
|
1195
|
+
// `parse_inlines` function, and the current subj->pos is pointing at the
|
1196
|
+
// closing ] brace, and because we copy all the text between the [ ]
|
1197
|
+
// braces, we should be able to safely ignore and delete any nodes after
|
1198
|
+
// the opener->inl_text->next.
|
1199
|
+
//
|
1200
|
+
// therefore, here we walk thru the list and free them all up
|
1201
|
+
cmark_node *next_node;
|
1202
|
+
cmark_node *current_node = opener->inl_text->next;
|
1203
|
+
while(current_node) {
|
1204
|
+
next_node = current_node->next;
|
1205
|
+
cmark_node_free(current_node);
|
1206
|
+
current_node = next_node;
|
1207
|
+
}
|
1208
|
+
|
1209
|
+
cmark_node_free(opener->inl_text);
|
1210
|
+
|
1153
1211
|
pop_bracket(subj);
|
1154
1212
|
return NULL;
|
1155
1213
|
}
|
data/ext/commonmarker/node.h
CHANGED
data/ext/commonmarker/table.c
CHANGED
@@ -114,60 +114,94 @@ static cmark_strbuf *unescape_pipes(cmark_mem *mem, unsigned char *string, bufsi
|
|
114
114
|
static table_row *row_from_string(cmark_syntax_extension *self,
|
115
115
|
cmark_parser *parser, unsigned char *string,
|
116
116
|
int len) {
|
117
|
+
// Parses a single table row. It has the following form:
|
118
|
+
// `delim? table_cell (delim table_cell)* delim? newline`
|
119
|
+
// Note that cells are allowed to be empty.
|
120
|
+
//
|
121
|
+
// From the GitHub-flavored Markdown specification:
|
122
|
+
//
|
123
|
+
// > Each row consists of cells containing arbitrary text, in which inlines
|
124
|
+
// > are parsed, separated by pipes (|). A leading and trailing pipe is also
|
125
|
+
// > recommended for clarity of reading, and if there’s otherwise parsing
|
126
|
+
// > ambiguity.
|
127
|
+
|
117
128
|
table_row *row = NULL;
|
118
129
|
bufsize_t cell_matched = 1, pipe_matched = 1, offset;
|
119
|
-
int
|
130
|
+
int expect_more_cells = 1;
|
131
|
+
int row_end_offset = 0;
|
132
|
+
int int_overflow_abort = 0;
|
120
133
|
|
121
134
|
row = (table_row *)parser->mem->calloc(1, sizeof(table_row));
|
122
135
|
row->n_columns = 0;
|
123
136
|
row->cells = NULL;
|
124
137
|
|
138
|
+
// Scan past the (optional) leading pipe.
|
125
139
|
offset = scan_table_cell_end(string, len, 0);
|
126
140
|
|
127
141
|
// Parse the cells of the row. Stop if we reach the end of the input, or if we
|
128
142
|
// cannot detect any more cells.
|
129
|
-
while (offset < len &&
|
143
|
+
while (offset < len && expect_more_cells) {
|
130
144
|
cell_matched = scan_table_cell(string, len, offset);
|
131
145
|
pipe_matched = scan_table_cell_end(string, len, offset + cell_matched);
|
132
146
|
|
133
147
|
if (cell_matched || pipe_matched) {
|
134
|
-
|
148
|
+
// We are guaranteed to have a cell, since (1) either we found some
|
149
|
+
// content and cell_matched, or (2) we found an empty cell followed by a
|
150
|
+
// pipe.
|
151
|
+
cmark_strbuf *cell_buf = unescape_pipes(parser->mem, string + offset,
|
152
|
+
cell_matched);
|
153
|
+
cmark_strbuf_trim(cell_buf);
|
154
|
+
|
155
|
+
node_cell *cell = (node_cell *)parser->mem->calloc(1, sizeof(*cell));
|
156
|
+
cell->buf = cell_buf;
|
157
|
+
cell->start_offset = offset;
|
158
|
+
cell->end_offset = offset + cell_matched - 1;
|
159
|
+
|
160
|
+
while (cell->start_offset > 0 && string[cell->start_offset - 1] != '|') {
|
161
|
+
--cell->start_offset;
|
162
|
+
++cell->internal_offset;
|
163
|
+
}
|
164
|
+
|
165
|
+
// make sure we never wrap row->n_columns
|
166
|
+
// offset will != len and our exit will clean up as intended
|
167
|
+
if (row->n_columns == UINT16_MAX) {
|
168
|
+
int_overflow_abort = 1;
|
169
|
+
break;
|
170
|
+
}
|
171
|
+
row->n_columns += 1;
|
172
|
+
row->cells = cmark_llist_append(parser->mem, row->cells, cell);
|
173
|
+
}
|
135
174
|
|
136
|
-
|
137
|
-
|
175
|
+
offset += cell_matched + pipe_matched;
|
176
|
+
|
177
|
+
if (pipe_matched) {
|
178
|
+
expect_more_cells = 1;
|
179
|
+
} else {
|
180
|
+
// We've scanned the last cell. Check if we have reached the end of the row
|
181
|
+
row_end_offset = scan_table_row_end(string, len, offset);
|
182
|
+
offset += row_end_offset;
|
183
|
+
|
184
|
+
// If the end of the row is not the end of the input,
|
185
|
+
// the row is not a real row but potentially part of the paragraph
|
186
|
+
// preceding the table.
|
187
|
+
if (row_end_offset && offset != len) {
|
188
|
+
row->paragraph_offset = offset;
|
138
189
|
|
139
190
|
cmark_llist_free_full(parser->mem, row->cells, (cmark_free_func)free_table_cell);
|
140
191
|
row->cells = NULL;
|
141
192
|
row->n_columns = 0;
|
142
|
-
} else {
|
143
|
-
cmark_strbuf *cell_buf = unescape_pipes(parser->mem, string + offset,
|
144
|
-
cell_matched);
|
145
|
-
cmark_strbuf_trim(cell_buf);
|
146
|
-
|
147
|
-
node_cell *cell = (node_cell *)parser->mem->calloc(1, sizeof(*cell));
|
148
|
-
cell->buf = cell_buf;
|
149
|
-
cell->start_offset = offset;
|
150
|
-
cell->end_offset = cell_end_offset;
|
151
|
-
|
152
|
-
while (cell->start_offset > 0 && string[cell->start_offset - 1] != '|') {
|
153
|
-
--cell->start_offset;
|
154
|
-
++cell->internal_offset;
|
155
|
-
}
|
156
193
|
|
157
|
-
|
158
|
-
|
159
|
-
}
|
160
|
-
}
|
161
|
-
|
162
|
-
offset += cell_matched + pipe_matched;
|
194
|
+
// Scan past the (optional) leading pipe.
|
195
|
+
offset += scan_table_cell_end(string, len, offset);
|
163
196
|
|
164
|
-
|
165
|
-
|
166
|
-
|
197
|
+
expect_more_cells = 1;
|
198
|
+
} else {
|
199
|
+
expect_more_cells = 0;
|
200
|
+
}
|
167
201
|
}
|
168
202
|
}
|
169
203
|
|
170
|
-
if (offset != len ||
|
204
|
+
if (offset != len || row->n_columns == 0 || int_overflow_abort) {
|
171
205
|
free_table_row(parser->mem, row);
|
172
206
|
row = NULL;
|
173
207
|
}
|
@@ -199,8 +233,6 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
|
|
199
233
|
cmark_parser *parser,
|
200
234
|
cmark_node *parent_container,
|
201
235
|
unsigned char *input, int len) {
|
202
|
-
bufsize_t matched =
|
203
|
-
scan_table_start(input, len, cmark_parser_get_first_nonspace(parser));
|
204
236
|
cmark_node *table_header;
|
205
237
|
table_row *header_row = NULL;
|
206
238
|
table_row *marker_row = NULL;
|
@@ -208,41 +240,48 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
|
|
208
240
|
const char *parent_string;
|
209
241
|
uint16_t i;
|
210
242
|
|
211
|
-
if (!
|
212
|
-
return parent_container;
|
213
|
-
|
214
|
-
parent_string = cmark_node_get_string_content(parent_container);
|
215
|
-
|
216
|
-
cmark_arena_push();
|
217
|
-
|
218
|
-
header_row = row_from_string(self, parser, (unsigned char *)parent_string,
|
219
|
-
(int)strlen(parent_string));
|
220
|
-
|
221
|
-
if (!header_row) {
|
222
|
-
free_table_row(parser->mem, header_row);
|
223
|
-
cmark_arena_pop();
|
243
|
+
if (!scan_table_start(input, len, cmark_parser_get_first_nonspace(parser))) {
|
224
244
|
return parent_container;
|
225
245
|
}
|
226
246
|
|
247
|
+
// Since scan_table_start was successful, we must have a marker row.
|
227
248
|
marker_row = row_from_string(self, parser,
|
228
249
|
input + cmark_parser_get_first_nonspace(parser),
|
229
250
|
len - cmark_parser_get_first_nonspace(parser));
|
230
|
-
|
251
|
+
// assert may be optimized out, don't rely on it for security boundaries
|
252
|
+
if (!marker_row) {
|
253
|
+
return parent_container;
|
254
|
+
}
|
255
|
+
|
231
256
|
assert(marker_row);
|
232
257
|
|
233
|
-
|
234
|
-
|
258
|
+
cmark_arena_push();
|
259
|
+
|
260
|
+
// Check for a matching header row. We call `row_from_string` with the entire
|
261
|
+
// (potentially long) parent container as input, but this should be safe since
|
262
|
+
// `row_from_string` bails out early if it does not find a row.
|
263
|
+
parent_string = cmark_node_get_string_content(parent_container);
|
264
|
+
header_row = row_from_string(self, parser, (unsigned char *)parent_string,
|
265
|
+
(int)strlen(parent_string));
|
266
|
+
if (!header_row || header_row->n_columns != marker_row->n_columns) {
|
235
267
|
free_table_row(parser->mem, marker_row);
|
268
|
+
free_table_row(parser->mem, header_row);
|
236
269
|
cmark_arena_pop();
|
237
270
|
return parent_container;
|
238
271
|
}
|
239
272
|
|
240
273
|
if (cmark_arena_pop()) {
|
274
|
+
marker_row = row_from_string(
|
275
|
+
self, parser, input + cmark_parser_get_first_nonspace(parser),
|
276
|
+
len - cmark_parser_get_first_nonspace(parser));
|
241
277
|
header_row = row_from_string(self, parser, (unsigned char *)parent_string,
|
242
278
|
(int)strlen(parent_string));
|
243
|
-
|
244
|
-
|
245
|
-
|
279
|
+
// row_from_string can return NULL, add additional check to ensure n_columns match
|
280
|
+
if (!marker_row || !header_row || header_row->n_columns != marker_row->n_columns) {
|
281
|
+
free_table_row(parser->mem, marker_row);
|
282
|
+
free_table_row(parser->mem, header_row);
|
283
|
+
return parent_container;
|
284
|
+
}
|
246
285
|
}
|
247
286
|
|
248
287
|
if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) {
|
@@ -257,13 +296,13 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
|
|
257
296
|
}
|
258
297
|
|
259
298
|
cmark_node_set_syntax_extension(parent_container, self);
|
260
|
-
|
261
299
|
parent_container->as.opaque = parser->mem->calloc(1, sizeof(node_table));
|
262
|
-
|
263
300
|
set_n_table_columns(parent_container, header_row->n_columns);
|
264
301
|
|
302
|
+
// allocate alignments based on marker_row->n_columns
|
303
|
+
// since we populate the alignments array based on marker_row->cells
|
265
304
|
uint8_t *alignments =
|
266
|
-
(uint8_t *)parser->mem->calloc(
|
305
|
+
(uint8_t *)parser->mem->calloc(marker_row->n_columns, sizeof(uint8_t));
|
267
306
|
cmark_llist *it = marker_row->cells;
|
268
307
|
for (i = 0; it; it = it->next, ++i) {
|
269
308
|
node_cell *node = (node_cell *)it->data;
|
@@ -332,6 +371,12 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
|
|
332
371
|
row = row_from_string(self, parser, input + cmark_parser_get_first_nonspace(parser),
|
333
372
|
len - cmark_parser_get_first_nonspace(parser));
|
334
373
|
|
374
|
+
if (!row) {
|
375
|
+
// clean up the dangling node
|
376
|
+
cmark_node_free(table_row_block);
|
377
|
+
return NULL;
|
378
|
+
}
|
379
|
+
|
335
380
|
{
|
336
381
|
cmark_llist *tmp;
|
337
382
|
int i, table_columns = get_n_table_columns(parent_container);
|
data/lib/commonmarker/config.rb
CHANGED
@@ -7,23 +7,28 @@ module CommonMarker
|
|
7
7
|
OPTS = {
|
8
8
|
parse: {
|
9
9
|
DEFAULT: 0,
|
10
|
+
SOURCEPOS: (1 << 1),
|
11
|
+
UNSAFE: (1 << 17),
|
10
12
|
VALIDATE_UTF8: (1 << 9),
|
11
13
|
SMART: (1 << 10),
|
12
14
|
LIBERAL_HTML_TAG: (1 << 12),
|
13
15
|
FOOTNOTES: (1 << 13),
|
14
|
-
STRIKETHROUGH_DOUBLE_TILDE: (1 << 14)
|
15
|
-
UNSAFE: (1 << 17)
|
16
|
+
STRIKETHROUGH_DOUBLE_TILDE: (1 << 14)
|
16
17
|
}.freeze,
|
17
18
|
render: {
|
18
19
|
DEFAULT: 0,
|
19
20
|
SOURCEPOS: (1 << 1),
|
20
21
|
HARDBREAKS: (1 << 2),
|
22
|
+
UNSAFE: (1 << 17),
|
21
23
|
NOBREAKS: (1 << 4),
|
24
|
+
VALIDATE_UTF8: (1 << 9),
|
25
|
+
SMART: (1 << 10),
|
22
26
|
GITHUB_PRE_LANG: (1 << 11),
|
27
|
+
LIBERAL_HTML_TAG: (1 << 12),
|
28
|
+
FOOTNOTES: (1 << 13),
|
29
|
+
STRIKETHROUGH_DOUBLE_TILDE: (1 << 14),
|
23
30
|
TABLE_PREFER_STYLE_ATTRIBUTES: (1 << 15),
|
24
|
-
FULL_INFO_STRING: (1 << 16)
|
25
|
-
UNSAFE: (1 << 17),
|
26
|
-
FOOTNOTES: (1 << 13)
|
31
|
+
FULL_INFO_STRING: (1 << 16)
|
27
32
|
}.freeze,
|
28
33
|
format: %i[html xml commonmark plaintext].freeze
|
29
34
|
}.freeze
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'commonmarker/node/inspect'
|
4
|
+
|
5
|
+
module CommonMarker
|
6
|
+
class RenderError < StandardError
|
7
|
+
PREAMBLE = 'There was an error rendering'
|
8
|
+
def initialize(error)
|
9
|
+
super("#{PREAMBLE}: #{error.class} #{error.message}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/commonmarker/version.rb
CHANGED
data/lib/commonmarker.rb
CHANGED
@@ -23,9 +23,7 @@ module CommonMarker
|
|
23
23
|
raise TypeError, "text must be a String; got a #{text.class}!" unless text.is_a?(String)
|
24
24
|
|
25
25
|
opts = Config.process_options(options, :render)
|
26
|
-
text
|
27
|
-
html = Node.markdown_to_html(text, opts, extensions)
|
28
|
-
html.force_encoding('UTF-8')
|
26
|
+
Node.markdown_to_html(text.encode('UTF-8'), opts, extensions)
|
29
27
|
end
|
30
28
|
|
31
29
|
# Public: Parses a Markdown string into a `document` node.
|
data/test/benchmark.rb
CHANGED
@@ -1,32 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'benchmark/ips'
|
3
4
|
require 'commonmarker'
|
4
|
-
require 'github/markdown'
|
5
5
|
require 'redcarpet'
|
6
6
|
require 'kramdown'
|
7
7
|
require 'benchmark'
|
8
8
|
|
9
|
-
|
10
|
-
puts name
|
11
|
-
puts Benchmark.measure(&blk)
|
12
|
-
end
|
9
|
+
benchinput = File.read('test/benchinput.md').freeze
|
13
10
|
|
14
|
-
|
11
|
+
printf("input size = %<bytes>d bytes\n\n", { bytes: benchinput.bytesize })
|
15
12
|
|
16
|
-
|
13
|
+
Benchmark.ips do |x|
|
14
|
+
x.report('redcarpet') do
|
15
|
+
Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: false, tables: false).render(benchinput)
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
end
|
18
|
+
x.report('commonmarker with to_html') do
|
19
|
+
CommonMarker.render_html(benchinput)
|
20
|
+
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
22
|
+
x.report('commonmarker with to_xml') do
|
23
|
+
CommonMarker.render_html(benchinput)
|
24
|
+
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
end
|
26
|
+
x.report('commonmarker with ruby HtmlRenderer') do
|
27
|
+
CommonMarker::HtmlRenderer.new.render(CommonMarker.render_doc(benchinput))
|
28
|
+
end
|
29
|
+
|
30
|
+
x.report('commonmarker with render_doc.to_html') do
|
31
|
+
CommonMarker.render_doc(benchinput, :DEFAULT, [:autolink]).to_html(:DEFAULT, [:autolink])
|
32
|
+
end
|
33
|
+
|
34
|
+
x.report('kramdown') do
|
35
|
+
Kramdown::Document.new(benchinput).to_html(benchinput)
|
36
|
+
end
|
29
37
|
|
30
|
-
|
31
|
-
Kramdown::Document.new(benchinput).to_html(benchinput)
|
38
|
+
x.compare!
|
32
39
|
end
|
data/test/test_basics.rb
CHANGED
@@ -15,4 +15,21 @@ class TestBasics < Minitest::Test
|
|
15
15
|
html = CommonMarker.render_html('Hi *there*')
|
16
16
|
assert_equal "<p>Hi <em>there</em></p>\n", html
|
17
17
|
end
|
18
|
+
|
19
|
+
# basic test that just checks if every option is accepted & no errors are thrown
|
20
|
+
def test_accept_every_option
|
21
|
+
text = "Hello **world** -- how are _you_ today? I'm ~~fine~~, ~yourself~?"
|
22
|
+
parse_opt = %i[SOURCEPOS UNSAFE VALIDATE_UTF8 SMART LIBERAL_HTML_TAG FOOTNOTES STRIKETHROUGH_DOUBLE_TILDE]
|
23
|
+
render_opt = parse_opt + %i[HARDBREAKS NOBREAKS GITHUB_PRE_LANG TABLE_PREFER_STYLE_ATTRIBUTES FULL_INFO_STRING]
|
24
|
+
|
25
|
+
extensions = %i[table tasklist strikethrough autolink tagfilter]
|
26
|
+
|
27
|
+
assert_equal "<p>Hello <strong>world</strong> – how are <em>you</em> today? I’m <del>fine</del>, ~yourself~?</p>\n", CommonMarker.render_doc(text, parse_opt, extensions).to_html
|
28
|
+
|
29
|
+
# NOTE: how tho the doc returned has sourcepos info, by default the renderer
|
30
|
+
# won't emit it. for that we need to pass in the render opt
|
31
|
+
assert_equal "<p data-sourcepos=\"1:1-1:65\">Hello <strong>world</strong> – how are <em>you</em> today? I’m <del>fine</del>, ~yourself~?</p>\n", CommonMarker.render_doc(text, parse_opt, extensions).to_html(render_opt, extensions)
|
32
|
+
|
33
|
+
assert_equal "<p data-sourcepos=\"1:1-1:65\">Hello <strong>world</strong> – how are <em>you</em> today? I’m <del>fine</del>, ~yourself~?</p>\n", CommonMarker.render_html(text, parse_opt, extensions)
|
34
|
+
end
|
18
35
|
end
|
data/test/test_extensions.rb
CHANGED
@@ -29,6 +29,9 @@ class TestExtensions < Minitest::Test
|
|
29
29
|
doc = CommonMarker.render_doc('~a~ ~~b~~ ~~~c~~~', :STRIKETHROUGH_DOUBLE_TILDE, [:strikethrough])
|
30
30
|
assert_equal("<p>~a~ <del>b</del> ~~~c~~~</p>\n", doc.to_html)
|
31
31
|
|
32
|
+
html = CommonMarker.render_html('~a~ ~~b~~ ~~~c~~~', :STRIKETHROUGH_DOUBLE_TILDE, [:strikethrough])
|
33
|
+
assert_equal("<p>~a~ <del>b</del> ~~~c~~~</p>\n", html)
|
34
|
+
|
32
35
|
CommonMarker.render_html(@markdown, :DEFAULT, %i[table strikethrough]).tap do |out|
|
33
36
|
refute_includes out, '| a'
|
34
37
|
refute_includes out, '| <strong>x</strong>'
|