mirror of
https://github.com/danbulant/markdown-wasm
synced 2026-05-27 05:51:45 +00:00
Disallow "javascript:" URIs in links. Adds option allowJSURIs to explicitly allow it. Closes #14
This commit is contained in:
parent
571f5ceb09
commit
4b48783c3e
8 changed files with 45 additions and 28 deletions
|
|
@ -79,3 +79,4 @@ breaks.</p>
|
||||||
<h2><a id="anot-her" class="anchor" aria-hidden="true" href="#anot-her"></a>Anöt######her!</h2>
|
<h2><a id="anot-her" class="anchor" aria-hidden="true" href="#anot-her"></a>Anöt######her!</h2>
|
||||||
<h2><a id="anot-her" class="anchor" aria-hidden="true" href="#anot-her"></a>?!Anöt//her!!</h2>
|
<h2><a id="anot-her" class="anchor" aria-hidden="true" href="#anot-her"></a>?!Anöt//her!!</h2>
|
||||||
<h2><a id="" class="anchor" aria-hidden="true" href="#"></a>?!!</h2>
|
<h2><a id="" class="anchor" aria-hidden="true" href="#"></a>?!!</h2>
|
||||||
|
<p><a href="">XSS test</a></p>
|
||||||
|
|
|
||||||
|
|
@ -82,3 +82,5 @@ function codeBlocks() {
|
||||||
## ?!Anöt//her!!
|
## ?!Anöt//her!!
|
||||||
|
|
||||||
## ?!!
|
## ?!!
|
||||||
|
|
||||||
|
[XSS test](javAscRipt:alert("xss"))
|
||||||
|
|
|
||||||
5
markdown.d.ts
vendored
5
markdown.d.ts
vendored
|
|
@ -28,8 +28,11 @@ export interface ParseOptions {
|
||||||
*/
|
*/
|
||||||
bytes? :boolean
|
bytes? :boolean
|
||||||
|
|
||||||
|
/** Allow "javascript:" in links */
|
||||||
|
allowJSURIs? :boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* onCodeBlock is an optional callback which if provided is called for each code block.
|
* Optional callback which if provided is called for each code block.
|
||||||
* langname holds the "language tag", if any, of the block.
|
* langname holds the "language tag", if any, of the block.
|
||||||
*
|
*
|
||||||
* The returned value is inserted into the resulting HTML verbatim, without HTML escaping.
|
* The returned value is inserted into the resulting HTML verbatim, without HTML escaping.
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,13 @@ typedef int32_t i32;
|
||||||
|
|
||||||
#include "wbuf.h"
|
#include "wbuf.h"
|
||||||
|
|
||||||
|
// these should be in sync with "OutputFlags" in md.js
|
||||||
|
typedef enum OutputFlags {
|
||||||
|
OutputFlagHTML = 1 << 0,
|
||||||
|
OutputFlagXHTML = 1 << 1,
|
||||||
|
OutputFlagAllowJSURI = 1 << 2, // allow "javascript:" URIs in links
|
||||||
|
} OutputFlags;
|
||||||
|
|
||||||
typedef int(*JSTextFilterFun)(
|
typedef int(*JSTextFilterFun)(
|
||||||
const char* metaptr, u32 metalen,
|
const char* metaptr, u32 metalen,
|
||||||
const char* inptr, u32 inlen,
|
const char* inptr, u32 inlen,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "fmt_html.h"
|
#include "fmt_html.h"
|
||||||
|
|
@ -257,9 +258,21 @@ static void render_open_td_block(FmtHTML* r, bool isTH, const MD_BLOCK_TD_DETAIL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool is_javascript_uri(const MD_CHAR* text, size_t len) {
|
||||||
|
return (
|
||||||
|
len >= strlen("javascript:") &&
|
||||||
|
strncasecmp(text, "javascript:", strlen("javascript:")) == 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static void render_open_a_span(FmtHTML* r, const MD_SPAN_A_DETAIL* det) {
|
static void render_open_a_span(FmtHTML* r, const MD_SPAN_A_DETAIL* det) {
|
||||||
render_literal(r, "<a href=\"");
|
render_literal(r, "<a href=\"");
|
||||||
render_attribute(r, &det->href);
|
// skip "javascript:" URIs unless explicitly allowed
|
||||||
|
if ((r->flags & OutputFlagAllowJSURI) != 0 ||
|
||||||
|
!is_javascript_uri(det->href.text, det->href.size))
|
||||||
|
{
|
||||||
|
render_attribute(r, &det->href);
|
||||||
|
}
|
||||||
if (det->title.text != NULL) {
|
if (det->title.text != NULL) {
|
||||||
render_literal(r, "\" title=\"");
|
render_literal(r, "\" title=\"");
|
||||||
render_attribute(r, &det->title);
|
render_attribute(r, &det->title);
|
||||||
|
|
@ -279,7 +292,7 @@ static void render_close_img_span(FmtHTML* r, const MD_SPAN_IMG_DETAIL* det) {
|
||||||
render_literal(r, "\" title=\"");
|
render_literal(r, "\" title=\"");
|
||||||
render_attribute(r, &det->title);
|
render_attribute(r, &det->title);
|
||||||
}
|
}
|
||||||
render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "\"/>" : "\">");
|
render_literal(r, (r->flags & OutputFlagXHTML) ? "\"/>" : "\">");
|
||||||
r->imgnest--;
|
r->imgnest--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,7 +319,7 @@ static int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
|
||||||
case MD_BLOCK_UL: render_literal(r, "<ul>\n"); break;
|
case MD_BLOCK_UL: render_literal(r, "<ul>\n"); break;
|
||||||
case MD_BLOCK_OL: render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
|
case MD_BLOCK_OL: render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
|
||||||
case MD_BLOCK_LI: render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
|
case MD_BLOCK_LI: render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
|
||||||
case MD_BLOCK_HR: render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr/>\n" : "<hr>\n"); break;
|
case MD_BLOCK_HR: render_literal(r, (r->flags & OutputFlagXHTML) ? "<hr/>\n" : "<hr>\n"); break;
|
||||||
case MD_BLOCK_H:
|
case MD_BLOCK_H:
|
||||||
{
|
{
|
||||||
render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]);
|
render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]);
|
||||||
|
|
@ -379,8 +392,8 @@ static int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
||||||
case MD_SPAN_EM: render_literal(r, "<em>"); break;
|
case MD_SPAN_EM: render_literal(r, "<em>"); break;
|
||||||
case MD_SPAN_STRONG: render_literal(r, "<b>"); break;
|
case MD_SPAN_STRONG: render_literal(r, "<b>"); break;
|
||||||
case MD_SPAN_U: render_literal(r, "<u>"); break;
|
case MD_SPAN_U: render_literal(r, "<u>"); break;
|
||||||
case MD_SPAN_A: render_open_a_span(r, (MD_SPAN_A_DETAIL*) detail); break;
|
case MD_SPAN_A: render_open_a_span(r, (MD_SPAN_A_DETAIL*)detail); break;
|
||||||
case MD_SPAN_IMG: render_open_img_span(r, (MD_SPAN_IMG_DETAIL*) detail); break;
|
case MD_SPAN_IMG: render_open_img_span(r, (MD_SPAN_IMG_DETAIL*)detail); break;
|
||||||
case MD_SPAN_CODE: render_literal(r, "<code>"); break;
|
case MD_SPAN_CODE: render_literal(r, "<code>"); break;
|
||||||
case MD_SPAN_DEL: render_literal(r, "<del>"); break;
|
case MD_SPAN_DEL: render_literal(r, "<del>"); break;
|
||||||
case MD_SPAN_LATEXMATH: render_literal(r, "<x-equation>"); break;
|
case MD_SPAN_LATEXMATH: render_literal(r, "<x-equation>"); break;
|
||||||
|
|
@ -452,12 +465,12 @@ static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, vo
|
||||||
render_literal(
|
render_literal(
|
||||||
r,
|
r,
|
||||||
r->imgnest == 0 ?
|
r->imgnest == 0 ?
|
||||||
((r->flags & MD_HTML_FLAG_XHTML) ? "<br/>\n" : "<br>\n") :
|
((r->flags & OutputFlagXHTML) ? "<br/>\n" : "<br>\n") :
|
||||||
" "
|
" "
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr/>\n" : "<hr>\n"); break;
|
render_literal(r, (r->flags & OutputFlagXHTML) ? "<hr/>\n" : "<hr>\n"); break;
|
||||||
|
|
||||||
case MD_TEXT_SOFTBR: render_literal(r, (r->imgnest == 0 ? "\n" : " ")); break;
|
case MD_TEXT_SOFTBR: render_literal(r, (r->imgnest == 0 ? "\n" : " ")); break;
|
||||||
case MD_TEXT_HTML: render_text(r, text, size); break;
|
case MD_TEXT_HTML: render_text(r, text, size); break;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MD_HTML_FLAG_XHTML 0x0008 // instead of e.g. <br>, generate <br/>
|
|
||||||
|
|
||||||
typedef struct FmtHTML {
|
typedef struct FmtHTML {
|
||||||
u32 flags; // MD_HTML_FLAG_*
|
OutputFlags flags;
|
||||||
u32 parserFlags; // passed along to md_parse
|
u32 parserFlags; // passed along to md_parse
|
||||||
WBuf* outbuf;
|
WBuf* outbuf;
|
||||||
|
|
||||||
// optional callbacks
|
// optional callbacks
|
||||||
JSTextFilterFun onCodeBlock;
|
JSTextFilterFun onCodeBlock;
|
||||||
|
|
|
||||||
13
src/md.c
13
src/md.c
|
|
@ -4,12 +4,6 @@
|
||||||
#include "fmt_html.h"
|
#include "fmt_html.h"
|
||||||
// #include "fmt_json.h"
|
// #include "fmt_json.h"
|
||||||
|
|
||||||
// these should be in sync with "OutputFlags" in md.js
|
|
||||||
typedef enum OutputFlags {
|
|
||||||
OutputFlagHTML = 1 << 0,
|
|
||||||
OutputFlagXHTML = 1 << 1,
|
|
||||||
} OutputFlags;
|
|
||||||
|
|
||||||
typedef enum ErrorCode {
|
typedef enum ErrorCode {
|
||||||
ERR_NONE,
|
ERR_NONE,
|
||||||
ERR_MD_PARSE,
|
ERR_MD_PARSE,
|
||||||
|
|
@ -41,19 +35,16 @@ export size_t parseUTF8(
|
||||||
|
|
||||||
WBufReset(&outbuf);
|
WBufReset(&outbuf);
|
||||||
|
|
||||||
if (outflags & OutputFlagHTML) {
|
if ((outflags & OutputFlagHTML) || (outflags & OutputFlagXHTML)) {
|
||||||
WBufReserve(&outbuf, inbuflen * 2); // approximate output size to minimize reallocations
|
WBufReserve(&outbuf, inbuflen * 2); // approximate output size to minimize reallocations
|
||||||
|
|
||||||
FmtHTML fmt = {
|
FmtHTML fmt = {
|
||||||
.flags = 0,
|
.flags = outflags,
|
||||||
.parserFlags = parser_flags,
|
.parserFlags = parser_flags,
|
||||||
.outbuf = &outbuf,
|
.outbuf = &outbuf,
|
||||||
.onCodeBlock = onCodeBlock,
|
.onCodeBlock = onCodeBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (outflags & OutputFlagXHTML)
|
|
||||||
fmt.flags |= MD_HTML_FLAG_XHTML;
|
|
||||||
|
|
||||||
if (fmt_html(inbufptr, inbuflen, &fmt) != 0) {
|
if (fmt_html(inbufptr, inbuflen, &fmt) != 0) {
|
||||||
// fmt_html returns status of md_parse which only fails in extreme cases
|
// fmt_html returns status of md_parse which only fails in extreme cases
|
||||||
// like when out of memory. md4c does not provide error codes or error messages.
|
// like when out of memory. md4c does not provide error codes or error messages.
|
||||||
|
|
|
||||||
10
src/md.js
10
src/md.js
|
|
@ -41,10 +41,11 @@ export const ParseFlags = {
|
||||||
NO_HTML: 0x0020 | 0x0040, // NO_HTML_BLOCKS | NO_HTML_SPANS
|
NO_HTML: 0x0020 | 0x0040, // NO_HTML_BLOCKS | NO_HTML_SPANS
|
||||||
}
|
}
|
||||||
|
|
||||||
// these should be in sync with "OutputFlags" in md.c
|
// these should be in sync with "OutputFlags" in common.h
|
||||||
const OutputFlags = {
|
const OutputFlags = {
|
||||||
HTML: 1 << 0, // Output HTML
|
HTML: 1 << 0, // Output HTML
|
||||||
XHTML: 1 << 1, // Output XHTML (only has effect with HTML flag set)
|
XHTML: 1 << 1, // Output XHTML (only has effect with HTML flag set)
|
||||||
|
AllowJSURI: 1 << 2, // Allow "javascript:" URIs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -56,7 +57,8 @@ export function parse(source, options) {
|
||||||
options.parseFlags
|
options.parseFlags
|
||||||
)
|
)
|
||||||
|
|
||||||
let outputFlags = 0
|
let outputFlags = options.allowJSURIs ? OutputFlags.AllowJSURI : 0
|
||||||
|
|
||||||
switch (options.format) {
|
switch (options.format) {
|
||||||
case "xhtml":
|
case "xhtml":
|
||||||
outputFlags |= OutputFlags.HTML | OutputFlags.XHTML
|
outputFlags |= OutputFlags.HTML | OutputFlags.XHTML
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue