Browse Source

Normalize links before they hit renderer

pull/14/head
Alex Kocharin 10 years ago
parent
commit
d54ed887f4
  1. 9
      lib/helpers/normalize_link.js
  2. 12
      lib/helpers/parse_link_destination.js
  3. 4
      lib/renderer.js
  4. 12
      lib/rules_inline/autolink.js
  5. 16
      test/fixtures/remarkable/commonmark_extras.txt

9
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)));
};

12
lib/helpers/parse_link_destination.js

@ -6,11 +6,12 @@
'use strict'; 'use strict';
var normalizeLink = require('./normalize_link');
var unescapeMd = require('../common/utils').unescapeMd; var unescapeMd = require('../common/utils').unescapeMd;
module.exports = function parseLinkDestination(state, pos) { module.exports = function parseLinkDestination(state, pos) {
var code, level, var code, level, link,
start = pos, start = pos,
max = state.posMax; max = state.posMax;
@ -20,8 +21,10 @@ module.exports = function parseLinkDestination(state, pos) {
code = state.src.charCodeAt(pos); code = state.src.charCodeAt(pos);
if (code === 0x0A /* \n */) { return false; } if (code === 0x0A /* \n */) { return false; }
if (code === 0x3E /* > */) { if (code === 0x3E /* > */) {
link = normalizeLink(unescapeMd(state.src.slice(start + 1, pos)));
if (!state.parser.validateLink(link)) { return false; }
state.pos = pos + 1; state.pos = pos + 1;
state.linkContent = unescapeMd(state.src.slice(start + 1, pos)); state.linkContent = link;
return true; return true;
} }
if (code === 0x5C /* \ */ && pos + 1 < max) { if (code === 0x5C /* \ */ && pos + 1 < max) {
@ -67,9 +70,10 @@ module.exports = function parseLinkDestination(state, pos) {
if (start === pos) { return false; } if (start === pos) { return false; }
state.linkContent = unescapeMd(state.src.slice(start, pos)); link = normalizeLink(unescapeMd(state.src.slice(start, pos)));
if (!state.parser.validateLink(state.linkContent)) { return false; } if (!state.parser.validateLink(link)) { return false; }
state.linkContent = link;
state.pos = pos; state.pos = pos;
return true; return true;
}; };

4
lib/renderer.js

@ -147,7 +147,7 @@ rules.paragraph_close = function (tokens, idx /*, options, env */) {
rules.link_open = function (tokens, idx /*, options, env */) { rules.link_open = function (tokens, idx /*, options, env */) {
var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : ''; var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
return '<a href="' + escapeHtml(encodeURI(decodeURI(replaceEntities(tokens[idx].href)))) + '"' + title + '>'; return '<a href="' + escapeHtml(tokens[idx].href) + '"' + title + '>';
}; };
rules.link_close = function (/* tokens, idx, options, env */) { rules.link_close = function (/* tokens, idx, options, env */) {
return '</a>'; return '</a>';
@ -155,7 +155,7 @@ rules.link_close = function (/* tokens, idx, options, env */) {
rules.image = 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 title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
var alt = ' alt="' + (tokens[idx].alt ? escapeHtml(replaceEntities(tokens[idx].alt)) : '') + '"'; var alt = ' alt="' + (tokens[idx].alt ? escapeHtml(replaceEntities(tokens[idx].alt)) : '') + '"';
var suffix = options.xhtmlOut ? ' /' : ''; var suffix = options.xhtmlOut ? ' /' : '';

12
lib/rules_inline/autolink.js

@ -3,6 +3,7 @@
'use strict'; '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*/ /*eslint max-len:0*/
@ -11,7 +12,7 @@ var AUTOLINK_RE = /^<([a-zA-Z.\-]{1,25}):([^<>\x00-\x20]*)>/;
module.exports = function autolink(state, silent) { 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; } 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; } if (url_schemas.indexOf(linkMatch[1].toLowerCase()) < 0) { return false; }
url = linkMatch[0].slice(1, -1); url = linkMatch[0].slice(1, -1);
fullUrl = normalizeLink(url);
if (!state.parser.validateLink(url)) { return false; } if (!state.parser.validateLink(url)) { return false; }
if (!silent) { if (!silent) {
state.push({ state.push({
type: 'link_open', type: 'link_open',
href: url, href: fullUrl,
level: state.level level: state.level
}); });
state.push({ state.push({
@ -52,12 +53,13 @@ module.exports = function autolink(state, silent) {
url = emailMatch[0].slice(1, -1); 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) { if (!silent) {
state.push({ state.push({
type: 'link_open', type: 'link_open',
href: 'mailto:' + url, href: fullUrl,
level: state.level level: state.level
}); });
state.push({ state.push({

16
test/fixtures/remarkable/commonmark_extras.txt

@ -107,3 +107,19 @@ a
<p>a</p> <p>a</p>
<?php <?php
. .
Normalize link destination, but not text inside it:
.
<http://example.com/α%CE%B2γ%CE%B4>
.
<p><a href="http://example.com/%CE%B1%CE%B2%CE%B3%CE%B4">http://example.com/α%CE%B2γ%CE%B4</a></p>
.
Autolinks do not allow escaping:
.
<http://example.com/\[\>
.
<p><a href="http://example.com/%5C%5B%5C">http://example.com/\[\</a></p>
.

Loading…
Cancel
Save