cmark
My personal build of CMark ✏️
git clone: git://git.pablopie.xyz/cmark
Commit
ac39623d667999cfae1444b46508a9a423b0df1b
Parent
6dcd2beafdfbc9f694916bcdfa822b896aa44177
Author
John MacFarlane <jgm@berkeley.edu >
Date
Mon, 13 Jul 2015 09:21:35 -0700
Added `CMARK_OPT_SAFE` option and `--safe` command-line flag.
* Added `CMARK_OPT_SAFE`. This option disables rendering of raw HTML
and potentially dangerous links.
* Added `--safe` option in command-line program.
* Updated `cmark.3` man page.
* Added `scan_dangerous_url` to scanners.
* In HTML, suppress rendering of raw HTML and potentially dangerous
links if `CMARK_OPT_SAFE`. 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).
* Added `api_test` for `OPT_CMARK_SAFE`.
* Rewrote `README.md` on security.
Diffstat
10 files changed, 422 insertions, 15 deletions
diff --git a/api_test/main.c b/api_test/main.c
@@ -714,6 +714,21 @@ numeric_entities(test_batch_runner *runner)
}
static void
+test_safe(test_batch_runner *runner)
+{
+ // Test safe mode
+ static const char raw_html[] =
+ "<div>\nhi\n</div>\n\n<a>hi</a>\n[link](JAVAscript:alert('hi'))\n![image](file:my.js)\n";
+ char *html = cmark_markdown_to_html(raw_html,
+ sizeof(raw_html) - 1,
+ CMARK_OPT_DEFAULT |
+ CMARK_OPT_SAFE);
+ STR_EQ(runner, html, "<!-- raw HTML omitted -->\n<p><!-- raw HTML omitted -->hi<!-- raw HTML omitted -->\n<a href=\"\">link</a>\n<img src=\"\" alt=\"image\" /></p>\n",
+ "input with raw HTML and dangerous links");
+ free(html);
+}
+
+static void
test_md_to_html(test_batch_runner *runner, const char *markdown,
const char *expected_html, const char *msg)
{
@@ -741,6 +756,7 @@ int main() {
line_endings(runner);
numeric_entities(runner);
test_cplusplus(runner);
+ test_safe(runner);
test_print_summary(runner);
retval = test_ok(runner) ? 0 : 1;
diff --git a/man/man3/cmark.3 b/man/man3/cmark.3
@@ -1,4 +1,4 @@
-.TH cmark 3 "July 12, 2015" "LOCAL" "Library Functions Manual"
+.TH cmark 3 "July 13, 2015" "LOCAL" "Library Functions Manual"
.SH
NAME
.PP
@@ -569,6 +569,22 @@ dashes.
Validate UTF\-8 in the input before parsing, replacing illegal sequences
with the replacement character U+FFFD.
+.PP
+.nf
+\fC
+.RS 0n
+#define CMARK_OPT_SAFE 32
+.RE
+\f[]
+.fi
+
+.PP
+Suppress raw HTML and unsafe links (\f[C]javascript:\f[],
+\f[C]vbscript:\f[], \f[C]file:\f[], and \f[C]data:\f[], except for
+\f[C]image/png\f[], \f[C]image/gif\f[], \f[C]image/jpeg\f[], or
+\f[C]image/webp\f[] mime types). Raw HTML is replaced by a placeholder
+HTML comment. Unsafe links are replaced by empty strings.
+
.SS
Version information
diff --git a/src/cmark.h b/src/cmark.h
@@ -516,6 +516,14 @@ char *cmark_render_latex(cmark_node *root, int options, int width);
*/
#define CMARK_OPT_VALIDATE_UTF8 16
+/** Suppress raw HTML and unsafe links (`javascript:`, `vbscript:`,
+ * `file:`, and `data:`, except for `image/png`, `image/gif`,
+ * `image/jpeg`, or `image/webp` mime types). Raw HTML is replaced
+ * by a placeholder HTML comment. Unsafe links are replaced by
+ * empty strings.
+ */
+#define CMARK_OPT_SAFE 32
+
/**
* ## Version information
*/
diff --git a/src/html.c b/src/html.c
@@ -8,6 +8,7 @@
#include "node.h"
#include "buffer.h"
#include "houdini.h"
+#include "scanners.h"
// Functions to convert cmark_nodes to HTML strings.
@@ -174,7 +175,13 @@ S_render_node(cmark_node *node, cmark_event_type ev_type,
case CMARK_NODE_HTML:
cr(html);
- cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
+ if (options & CMARK_OPT_SAFE) {
+ cmark_strbuf_puts(html, "<!-- raw HTML omitted -->");
+ } else {
+ cmark_strbuf_put(html, node->as.literal.data,
+ node->as.literal.len);
+ }
+ cr(html);
break;
case CMARK_NODE_HRULE:
@@ -228,7 +235,12 @@ S_render_node(cmark_node *node, cmark_event_type ev_type,
break;
case CMARK_NODE_INLINE_HTML:
- cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
+ if (options & CMARK_OPT_SAFE) {
+ cmark_strbuf_puts(html, "<!-- raw HTML omitted -->");
+ } else {
+ cmark_strbuf_put(html, node->as.literal.data,
+ node->as.literal.len);
+ }
break;
case CMARK_NODE_STRONG:
@@ -250,15 +262,19 @@ S_render_node(cmark_node *node, cmark_event_type ev_type,
case CMARK_NODE_LINK:
if (entering) {
cmark_strbuf_puts(html, "<a href=\"");
- houdini_escape_href(html, node->as.link.url.data,
- node->as.link.url.len);
+ if (!((options & CMARK_OPT_SAFE) &&
+ scan_dangerous_url(&node->as.link.url, 0))) {
+ houdini_escape_href(html,
+ node->as.link.url.data,
+ node->as.link.url.len);
+ }
if (node->as.link.title.len) {
cmark_strbuf_puts(html, "\" title=\"");
- escape_html(html, node->as.link.title.data,
- node->as.link.title.len);
+ escape_html(html,
+ node->as.link.title.data,
+ node->as.link.title.len);
}
-
cmark_strbuf_puts(html, "\">");
} else {
cmark_strbuf_puts(html, "</a>");
@@ -268,9 +284,13 @@ S_render_node(cmark_node *node, cmark_event_type ev_type,
case CMARK_NODE_IMAGE:
if (entering) {
cmark_strbuf_puts(html, "<img src=\"");
- houdini_escape_href(html, node->as.link.url.data,
- node->as.link.url.len);
+ if (!((options & CMARK_OPT_SAFE) &&
+ scan_dangerous_url(&node->as.link.url, 0))) {
+ houdini_escape_href(html,
+ node->as.link.url.data,
+ node->as.link.url.len);
+ }
cmark_strbuf_puts(html, "\" alt=\"");
state->plain = node;
} else {
diff --git a/src/main.c b/src/main.c
@@ -28,6 +28,7 @@ void print_usage()
printf(" --width WIDTH Specify wrap width (default 0 = nowrap)\n");
printf(" --sourcepos Include source position attribute\n");
printf(" --hardbreaks Treat newlines as hard line breaks\n");
+ printf(" --safe Suppress raw HTML and dangerous URLs\n");
printf(" --smart Use smart punctuation\n");
printf(" --normalize Consolidate adjacent text nodes\n");
printf(" --help, -h Print usage information\n");
@@ -93,6 +94,8 @@ int main(int argc, char *argv[])
options |= CMARK_OPT_HARDBREAKS;
} else if (strcmp(argv[i], "--smart") == 0) {
options |= CMARK_OPT_SMART;
+ } else if (strcmp(argv[i], "--safe") == 0) {
+ options |= CMARK_OPT_SAFE;
} else if (strcmp(argv[i], "--normalize") == 0) {
options |= CMARK_OPT_NORMALIZE;
} else if (strcmp(argv[i], "--validate-utf8") == 0) {
diff --git a/src/scanners.h b/src/scanners.h
@@ -26,6 +26,7 @@ bufsize_t _scan_hrule(const unsigned char *p);
bufsize_t _scan_open_code_fence(const unsigned char *p);
bufsize_t _scan_close_code_fence(const unsigned char *p);
bufsize_t _scan_entity(const unsigned char *p);
+bufsize_t _scan_dangerous_url(const unsigned char *p);
#define scan_scheme(c, n) _scan_at(&_scan_scheme, c, n)
#define scan_autolink_uri(c, n) _scan_at(&_scan_autolink_uri, c, n)
@@ -47,6 +48,7 @@ bufsize_t _scan_entity(const unsigned char *p);
#define scan_open_code_fence(c, n) _scan_at(&_scan_open_code_fence, c, n)
#define scan_close_code_fence(c, n) _scan_at(&_scan_close_code_fence, c, n)
#define scan_entity(c, n) _scan_at(&_scan_entity, c, n)
+#define scan_dangerous_url(c, n) _scan_at(&_scan_dangerous_url, c, n)
#ifdef __cplusplus
}