diff --git a/lib/helpers/normalize_link.js b/lib/helpers/normalize_link.js new file mode 100644 index 0000000..557f7b9 --- /dev/null +++ b/lib/helpers/normalize_link.js @@ -0,0 +1,9 @@ +'use strict'; + + +var replaceEntities = require('../common/utils').replaceEntities; + + +module.exports = function normalizeLink(url) { + return encodeURI(decodeURI(replaceEntities(url))); +}; diff --git a/lib/helpers/parse_link_destination.js b/lib/helpers/parse_link_destination.js index 2687f50..aece26c 100644 --- a/lib/helpers/parse_link_destination.js +++ b/lib/helpers/parse_link_destination.js @@ -6,11 +6,12 @@ 'use strict'; -var unescapeMd = require('../common/utils').unescapeMd; +var normalizeLink = require('./normalize_link'); +var unescapeMd = require('../common/utils').unescapeMd; module.exports = function parseLinkDestination(state, pos) { - var code, level, + var code, level, link, start = pos, max = state.posMax; @@ -20,8 +21,10 @@ module.exports = function parseLinkDestination(state, pos) { code = state.src.charCodeAt(pos); if (code === 0x0A /* \n */) { return false; } if (code === 0x3E /* > */) { + link = normalizeLink(unescapeMd(state.src.slice(start + 1, pos))); + if (!state.parser.validateLink(link)) { return false; } state.pos = pos + 1; - state.linkContent = unescapeMd(state.src.slice(start + 1, pos)); + state.linkContent = link; return true; } if (code === 0x5C /* \ */ && pos + 1 < max) { @@ -67,9 +70,10 @@ module.exports = function parseLinkDestination(state, pos) { if (start === pos) { return false; } - state.linkContent = unescapeMd(state.src.slice(start, pos)); - if (!state.parser.validateLink(state.linkContent)) { return false; } + link = normalizeLink(unescapeMd(state.src.slice(start, pos))); + if (!state.parser.validateLink(link)) { return false; } + state.linkContent = link; state.pos = pos; return true; }; diff --git a/lib/renderer.js b/lib/renderer.js index f0f8b8e..4c1ff9a 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -147,7 +147,7 @@ rules.paragraph_close = function (tokens, idx /*, options, env */) { rules.link_open = function (tokens, idx /*, options, env */) { var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : ''; - return ''; + return ''; }; rules.link_close = function (/* tokens, idx, options, env */) { return ''; @@ -155,7 +155,7 @@ rules.link_close = function (/* tokens, idx, options, env */) { rules.image = function (tokens, idx, options /*, env */) { - var src = ' src="' + escapeHtml(encodeURI(tokens[idx].src)) + '"'; + var src = ' src="' + escapeHtml(tokens[idx].src) + '"'; var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : ''; var alt = ' alt="' + (tokens[idx].alt ? escapeHtml(replaceEntities(tokens[idx].alt)) : '') + '"'; var suffix = options.xhtmlOut ? ' /' : ''; diff --git a/lib/rules_inline/autolink.js b/lib/rules_inline/autolink.js index 4669d59..e63b9aa 100644 --- a/lib/rules_inline/autolink.js +++ b/lib/rules_inline/autolink.js @@ -2,7 +2,8 @@ 'use strict'; -var url_schemas = require('../common/url_schemas'); +var url_schemas = require('../common/url_schemas'); +var normalizeLink = require('../helpers/normalize_link'); /*eslint max-len:0*/ @@ -11,7 +12,7 @@ var AUTOLINK_RE = /^<([a-zA-Z.\-]{1,25}):([^<>\x00-\x20]*)>/; module.exports = function autolink(state, silent) { - var tail, linkMatch, emailMatch, url, pos = state.pos; + var tail, linkMatch, emailMatch, url, fullUrl, pos = state.pos; if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } @@ -25,13 +26,13 @@ module.exports = function autolink(state, silent) { if (url_schemas.indexOf(linkMatch[1].toLowerCase()) < 0) { return false; } url = linkMatch[0].slice(1, -1); - + fullUrl = normalizeLink(url); if (!state.parser.validateLink(url)) { return false; } if (!silent) { state.push({ type: 'link_open', - href: url, + href: fullUrl, level: state.level }); state.push({ @@ -52,12 +53,13 @@ module.exports = function autolink(state, silent) { url = emailMatch[0].slice(1, -1); - if (!state.parser.validateLink('mailto:' + url)) { return false; } + fullUrl = normalizeLink('mailto:' + url); + if (!state.parser.validateLink(fullUrl)) { return false; } if (!silent) { state.push({ type: 'link_open', - href: 'mailto:' + url, + href: fullUrl, level: state.level }); state.push({ diff --git a/test/fixtures/remarkable/commonmark_extras.txt b/test/fixtures/remarkable/commonmark_extras.txt index 14ea06d..d9ed12e 100644 --- a/test/fixtures/remarkable/commonmark_extras.txt +++ b/test/fixtures/remarkable/commonmark_extras.txt @@ -107,3 +107,19 @@ a

a

+. +

http://example.com/α%CE%B2γ%CE%B4

+. + +Autolinks do not allow escaping: + +. + +. +

http://example.com/\[\

+.