Browse Source

Moved HTML escaping to renderer

pull/14/head
Vitaly Puzrin 10 years ago
parent
commit
8f909bcf33
  1. 22
      lib/common/utils.js
  2. 1
      lib/configs/commonmark.js
  3. 1
      lib/configs/default.js
  4. 4
      lib/parser_inline.js
  5. 28
      lib/renderer.js
  6. 5
      lib/rules_inline/autolink.js
  7. 7
      lib/rules_inline/entity.js
  8. 16
      lib/rules_inline/escape.js
  9. 20
      lib/rules_inline/escape_html_char.js
  10. 3
      lib/rules_text/linkify.js
  11. 2
      lib/rules_text/smartquotes.js

22
lib/common/utils.js

@ -30,27 +30,6 @@ function assign(obj /*from1, from2, from3, ...*/) {
} }
var HTML_ESCAPE_TEST_RE = /[&<>"]/;
var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g;
var HTML_REPLACEMENTS = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;'
};
function replaceUnsafeChar(ch) {
return HTML_REPLACEMENTS[ch];
}
function escapeHtml(str) {
if (HTML_ESCAPE_TEST_RE.test(str)) {
return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar);
}
return str;
}
var UNESCAPE_MD_RE = /\\([\\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g; var UNESCAPE_MD_RE = /\\([\\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;
function unescapeMd(str) { function unescapeMd(str) {
@ -104,7 +83,6 @@ function replaceEntities(str) {
exports.assign = assign; exports.assign = assign;
exports.isString = isString; exports.isString = isString;
exports.escapeHtml = escapeHtml;
exports.unescapeMd = unescapeMd; exports.unescapeMd = unescapeMd;
exports.isValidEntityCode = isValidEntityCode; exports.isValidEntityCode = isValidEntityCode;
exports.fromCodePoint = fromCodePoint; exports.fromCodePoint = fromCodePoint;

1
lib/configs/commonmark.js

@ -42,7 +42,6 @@ module.exports = {
'emphasis', 'emphasis',
'entity', 'entity',
'escape', 'escape',
'escape_html_char',
'htmltag', 'htmltag',
'links', 'links',
'newline', 'newline',

1
lib/configs/default.js

@ -44,7 +44,6 @@ module.exports = {
'emphasis', 'emphasis',
'entity', 'entity',
'escape', 'escape',
'escape_html_char',
'htmltag', 'htmltag',
'links', 'links',
'newline', 'newline',

4
lib/parser_inline.js

@ -25,7 +25,6 @@ rules.push(require('./rules_inline/links'));
rules.push(require('./rules_inline/autolink')); rules.push(require('./rules_inline/autolink'));
rules.push(require('./rules_inline/htmltag')); rules.push(require('./rules_inline/htmltag'));
rules.push(require('./rules_inline/entity')); rules.push(require('./rules_inline/entity'));
rules.push(require('./rules_inline/escape_html_char'));
var BAD_PROTOCOLS = [ 'vbscript', 'javascript', 'file' ]; var BAD_PROTOCOLS = [ 'vbscript', 'javascript', 'file' ];
@ -51,8 +50,7 @@ function ParserInline() {
// Rule to skip pure text // Rule to skip pure text
// - '{}$%@+=' reserved for extentions // - '{}$%@+=' reserved for extentions
// - '<>"' added for internal html escaping this.textMatch = /^[^\n\\`*_\[\]!&<{}$%@~+=]+/;
this.textMatch = /^[^\n\\`*_\[\]!&{}$%@<>"~+=]+/;
// By default CommonMark allows too much in links // By default CommonMark allows too much in links
// If you need to restrict it - override this with your validator. // If you need to restrict it - override this with your validator.

28
lib/renderer.js

@ -2,11 +2,13 @@
var assign = require('./common/utils').assign; var assign = require('./common/utils').assign;
var escapeHtml = require('./common/utils').escapeHtml;
var unescapeMd = require('./common/utils').unescapeMd; var unescapeMd = require('./common/utils').unescapeMd;
var replaceEntities = require('./common/utils').replaceEntities; var replaceEntities = require('./common/utils').replaceEntities;
////////////////////////////////////////////////////////////////////////////////
// Helpers
function escapeUrl(str) { function escapeUrl(str) {
try { try {
return encodeURI(str); return encodeURI(str);
@ -20,6 +22,26 @@ function unescapeUrl(str) {
return ''; return '';
} }
var HTML_ESCAPE_TEST_RE = /[&<>"]/;
var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g;
var HTML_REPLACEMENTS = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;'
};
function replaceUnsafeChar(ch) {
return HTML_REPLACEMENTS[ch];
}
function escapeHtml(str) {
if (HTML_ESCAPE_TEST_RE.test(str)) {
return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar);
}
return str;
}
// check if we need to hide '\n' before next token // check if we need to hide '\n' before next token
function getBreak(tokens, idx) { function getBreak(tokens, idx) {
@ -31,6 +53,8 @@ function getBreak(tokens, idx) {
return '\n'; return '\n';
} }
////////////////////////////////////////////////////////////////////////////////
var rules = {}; var rules = {};
@ -225,7 +249,7 @@ rules.softbreak = function (tokens, idx, options) {
rules.text = function (tokens, idx /*, options*/) { rules.text = function (tokens, idx /*, options*/) {
return tokens[idx].content; return escapeHtml(tokens[idx].content);
}; };

5
lib/rules_inline/autolink.js

@ -1,7 +1,6 @@
// Process autolinks '<protocol:...>' // Process autolinks '<protocol:...>'
var escapeHtml = require('../common/utils').escapeHtml;
var url_schemas = require('../common/url_schemas'); var url_schemas = require('../common/url_schemas');
@ -36,7 +35,7 @@ module.exports = function autolink(state, silent) {
}); });
state.push({ state.push({
type: 'text', type: 'text',
content: escapeHtml(url), content: url,
level: state.level + 1 level: state.level + 1
}); });
state.push({ type: 'link_close', level: state.level }); state.push({ type: 'link_close', level: state.level });
@ -62,7 +61,7 @@ module.exports = function autolink(state, silent) {
}); });
state.push({ state.push({
type: 'text', type: 'text',
content: escapeHtml(url), content: url,
level: state.level + 1 level: state.level + 1
}); });
state.push({ type: 'link_close', level: state.level }); state.push({ type: 'link_close', level: state.level });

7
lib/rules_inline/entity.js

@ -3,7 +3,6 @@
'use strict'; 'use strict';
var entities = require('../common/entities'); var entities = require('../common/entities');
var escapeHtml = require('../common/utils').escapeHtml;
var isValidEntityCode = require('../common/utils').isValidEntityCode; var isValidEntityCode = require('../common/utils').isValidEntityCode;
var fromCodePoint = require('../common/utils').fromCodePoint; var fromCodePoint = require('../common/utils').fromCodePoint;
@ -25,7 +24,7 @@ module.exports = function entity(state, silent) {
if (match) { if (match) {
if (!silent) { if (!silent) {
code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10); code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10);
state.pending += isValidEntityCode(code) ? escapeHtml(fromCodePoint(code)) : fromCodePoint(0xFFFD); state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD);
} }
state.pos += match[0].length; state.pos += match[0].length;
return true; return true;
@ -34,7 +33,7 @@ module.exports = function entity(state, silent) {
match = state.src.slice(pos).match(NAMED_RE); match = state.src.slice(pos).match(NAMED_RE);
if (match) { if (match) {
if (entities.hasOwnProperty(match[1])) { if (entities.hasOwnProperty(match[1])) {
if (!silent) { state.pending += escapeHtml(entities[match[1]]); } if (!silent) { state.pending += entities[match[1]]; }
state.pos += match[0].length; state.pos += match[0].length;
return true; return true;
} }
@ -42,7 +41,7 @@ module.exports = function entity(state, silent) {
} }
} }
if (!silent) { state.pending += '&amp;'; } if (!silent) { state.pending += '&'; }
state.pos++; state.pos++;
return true; return true;
}; };

16
lib/rules_inline/escape.js

@ -6,7 +6,7 @@ var ESCAPED = {};
.split('').forEach(function(ch) { ESCAPED[ch.charCodeAt(0)] = true; }); .split('').forEach(function(ch) { ESCAPED[ch.charCodeAt(0)] = true; });
module.exports = function escape(state, silent) { module.exports = function escape(state, silent) {
var ch, str, pos = state.pos, max = state.posMax; var ch, pos = state.pos, max = state.posMax;
if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; } if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; }
@ -16,19 +16,7 @@ module.exports = function escape(state, silent) {
ch = state.src.charCodeAt(pos); ch = state.src.charCodeAt(pos);
if (typeof ESCAPED[ch] !== 'undefined') { if (typeof ESCAPED[ch] !== 'undefined') {
// escape html chars if needed if (!silent) { state.pending += state.src[pos]; }
if (ch === 0x26/* & */) {
str = '&amp;';
} else if (ch === 0x3C/* < */) {
str = '&lt;';
} else if (ch === 0x3E/* > */) {
str = '&gt;';
} else if (ch === 0x22/* " */) {
str = '&quot;';
} else {
str = state.src[pos];
}
if (!silent) { state.pending += str; }
state.pos += 2; state.pos += 2;
return true; return true;
} }

20
lib/rules_inline/escape_html_char.js

@ -1,20 +0,0 @@
// Process < > " (& was processed in markdown escape)
module.exports = function escape_html_char(state, silent) {
var ch = state.src.charCodeAt(state.pos),
str;
if (ch === 0x3C/* < */) {
str = '&lt;';
} else if (ch === 0x3E/* > */) {
str = '&gt;';
} else if (ch === 0x22/* " */) {
str = '&quot;';
} else {
return false;
}
if (!silent) { state.pending += str; }
state.pos++;
return true;
};

3
lib/rules_text/linkify.js

@ -6,7 +6,6 @@
var Autolinker = require('autolinker'); var Autolinker = require('autolinker');
var escapeHtml = require('../common/utils').escapeHtml;
var links = []; var links = [];
@ -97,7 +96,7 @@ module.exports = function linkify(t, state) {
}); });
nodes.push({ nodes.push({
type: 'text', type: 'text',
content: escapeHtml(links[ln].text), content: links[ln].text,
level: level level: level
}); });
nodes.push({ nodes.push({

2
lib/rules_text/smartquotes.js

@ -3,7 +3,7 @@
'use strict'; 'use strict';
var quoteReg = /&quot;|'/g; var quoteReg = /['"]/g;
var punctReg = /[-\s()\[\]]/; var punctReg = /[-\s()\[\]]/;
var apostrophe = '’'; var apostrophe = '’';

Loading…
Cancel
Save