n_markdown regression: the safe subset, the opt-in block kinds, and the hostile-input safety cases.
n_markdown regression: the safe subset, the opt-in block kinds, and the hostile-input safety cases.
#include <string.h>
#define CHECK(cond, msg) \
do { \
if (!(cond)) { \
n_log(LOG_ERR, "FAIL: %s", (msg)); \
failures++; \
} \
} while (0)
return d;
}
return d;
}
size_t i, w = 0;
out[0] = '\0';
for (i = 0; i < inl->
n_spans; i++) {
size_t n = t ? strlen(t) : 0;
if (w + n + 1 > cap)
break;
memcpy(out + w, t, n);
w += n;
out[w] = '\0';
}
}
out[0] = '\0';
return;
}
{
unsigned seen = 0;
size_t i;
"bold+italic+code seen");
}
}
{
int link = 0, js_literal = 1;
size_t i;
link = 1;
"http url captured");
}
if (s->
url && strstr(s->
url,
"javascript"))
js_literal = 0;
}
CHECK(link,
"http link recognised");
CHECK(js_literal,
"javascript: scheme not turned into a link");
}
{
int quote = 0, code = 0, ul = 0;
size_t i;
quote = 1;
code = 1;
}
CHECK(quote && code && ul,
"quote + code block + 2-item list");
}
{
"ordered list of 3");
}
{
"has_markup false for plain");
"unclosed marker is not markup");
}
{
}
{
"heading is a paragraph in the default subset");
}
{
char buf[64];
CHECK(ok && strcmp(buf,
"Title") == 0,
"flagged heading, level 1, hashes stripped");
}
{
"six hashes is a level 6 heading");
}
{
}
{
"table is a paragraph in the default subset");
}
{
char buf[64];
CHECK(ok,
"flagged table: 2 columns, a header and two body rows");
if (ok) {
"alignment read from the rule row");
CHECK(strcmp(buf,
"Effect") == 0,
"header cell (0,1)");
CHECK(strcmp(buf,
"H") == 0,
"body cell (2,0)");
}
}
{
"table without edge pipes, centred second column");
}
{
"pipes with no rule row stay a paragraph");
}
{
"short and long rows are squared off to the header's width");
}
{
"a row of bare pipes parses and does not crash");
}
{
char buf[128];
CHECK(ok,
"a wrapped list item does not end its list");
if (ok) {
CHECK(strcmp(buf,
"two carries on onto a second line") == 0,
"the continuation joins with a space, indent stripped");
}
}
{
"a block start ends the item even when indented text preceded it");
}
{
#define IA N_MARKDOWN_INLINE_ALL
char buf[160];
struct {
const char* in;
const char* url;
} al[] = {
{"see https://x.org/a_b_c today", "https://x.org/a_b_c"},
{"see https://x.org.", "https://x.org"},
{"(https://x.org/w)", "https://x.org/w"},
{"https://en.wikipedia.org/wiki/C_(lang)", "https://en.wikipedia.org/wiki/C_(lang)"},
{"wow https://x.org!?", "https://x.org"},
{"<http://a>", "http://a"},
{"<https://x.org/q?a=1&b=2>", "https://x.org/q?a=1&b=2"},
{"HTTPS://X.ORG/P", "HTTPS://X.ORG/P"},
{"xhttps://x.org", NULL},
{"https://", NULL},
{"<https://>", NULL},
{"ftp://x.org", NULL},
{"`https://x.org`", NULL},
{"<javascript:alert(1)>", NULL},
};
size_t k;
for (k = 0; k < sizeof(al) / sizeof(al[0]); k++) {
const char* got = NULL;
size_t i;
"an autolink's text is its url");
}
if (al[k].url)
CHECK(got && strcmp(got, al[k].url) == 0, al[k].in);
else
CHECK(got == NULL, al[k].in);
}
{
CHECK(strcmp(buf,
"see https://x.org.") == 0,
"no character is lost around an autolink");
}
{
"an explicit link is not re-linked");
}
{
"~~x~~ is a strike span");
"strike nests with bold");
CHECK(strcmp(buf,
"~~~~ and ~one~") == 0 &&
"an empty or single-tilde strike is text");
d =
parse(
"a ~~gone~~ b");
}
{
strcmp(buf, "*not italic* and # not \\a") == 0,
"a backslash makes punctuation literal, and only punctuation");
"an escape is markup: the plain path would show it");
"but not unless asked for");
}
{
"the default parse links nothing bare");
}
#undef IA
}
{
char buf[96];
size_t k;
const char* lit[] = {"an **unclosed run", "a ** b", "a **** b",
"a `` b", "x __y"};
for (k = 0; k < sizeof(lit) / sizeof(lit[0]); k++) {
lit[k]);
}
{
"non-empty emphasis still works");
}
}
{
"a heading, a list item or a quote after a table is not a row");
"an ordinary line with a pipe is still a row");
}
{
char buf[128];
size_t k;
const char* lit[] = {":throwing_kiss: :wrapped_present: :two_hearts:",
"snake_case_name and other_name",
"café_au_lait_x", "a__b__c", "x_y _z"};
for (k = 0; k < sizeof(lit) / sizeof(lit[0]); k++) {
lit[k]);
}
{
CHECK(strcmp(buf,
"a_b c and x_y and abc") == 0 &&
"word-boundary underscores still emphasise; '*' is unchanged");
}
{
"punctuation is a boundary");
}
}
{
"a heading is markup when asked for");
"the default subset still counts under the flags");
}
return 1;
}
return 0;
}
static const char * row(N_GUI_TREE *tree, int index)
the text of a visible row, "" past the end
static n_http_req_status parse(const char *s, n_http_request *out)
static n_markdown_doc parse_ex(const char *s, unsigned flags)
static void flow_text(const n_markdown_inline *inl, char *out, size_t cap)
static void cell_text(const n_markdown_block *b, size_t row, size_t col, char *out, size_t cap)
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
#define LOG_NOTICE
normal but significant condition
void n_markdown_free(n_markdown_doc *doc)
free everything a n_markdown_doc owns and zero it
int n_markdown_has_markup_ex(const char *text, size_t len, unsigned flags)
1 if text contains any markup the given flags would render, else 0
void n_markdown_parse(const char *text, size_t len, n_markdown_doc *out)
parse text (len bytes, plain UTF-8) into out
void n_markdown_parse_ex(const char *text, size_t len, unsigned flags, n_markdown_doc *out)
parse text into out, choosing which optional block kinds to recognise
int n_markdown_has_markup(const char *text, size_t len)
1 if text contains any renderable markup, else 0
Parse a SAFE, strict subset of Markdown into a block / styled-span model.
#define N_MARKDOWN_CODE
inline code style flag (carries no emphasis)
#define N_MARKDOWN_ALIGN_CENTER
what "|:---:|" asks for.
#define N_MARKDOWN_ALIGN_RIGHT
what "|---:|" asks for.
#define N_MARKDOWN_ITALIC
italic inline style flag
#define N_MARKDOWN_ALL
every block kind this parser knows.
#define N_MARKDOWN_LINK
[text](http(s)://url) style flag; url is set only when this is present
#define N_MARKDOWN_BOLD
bold inline style flag
n_markdown_inline * items
#define N_MARKDOWN_TABLES
n_markdown_parse_ex flag: recognise GitHub-style pipe tables.
@ N_MARKDOWN_HEADING
"## " heading (inl, level)
@ N_MARKDOWN_OL
ordered list (items)
@ N_MARKDOWN_TABLE
pipe table (items, n_cols)
@ N_MARKDOWN_UL
unordered list (items)
@ N_MARKDOWN_CODE_BLOCK
``` fenced code (raw code)
@ N_MARKDOWN_PARA
paragraph
#define N_MARKDOWN_STRIKE
strike inline style flag (only with N_MARKDOWN_STRIKETHROUGH)
#define N_MARKDOWN_HEADINGS
n_markdown_parse_ex flag: recognise "# " to "###### " headings.
int kind
enum n_markdown_block_kind
unsigned char * align
TABLE: N_MARKDOWN_ALIGN_* per column, n_cols entries, owned.
#define N_MARKDOWN_ALIGN_LEFT
table column alignment: the default, and what "|:---|" asks for.
n_markdown_block * blocks
size_t n_cols
TABLE: cells per row, always at least 1.
int level
HEADING: 1 to 6.
A parsed document: an owned array of blocks.
One flow of inline runs (a paragraph, a quote, or one list item).
One inline run: visible text, a style bitmask, and (for a link) an already-http(s)-validated url.