/** * marked - a markdown parser * Copyright (c) 2011-2018, Christopher Jeffrey. (MIT Licensed) * https://github.com/markedjs/marked */ /** * Helpers */ export function escape(html, encode) { const escapeTest = /[&<>"']/; const escapeReplace = /[&<>"']/g; const replacements = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; if (encode) { if (escapeTest.test(html)) { return html.replace(escapeReplace, function(ch) { return replacements[ch]; }); } } else { if (escapeTestNoEncode.test(html)) { return html.replace(escapeReplaceNoEncode, function(ch) { return replacements[ch]; }); } } return html; } export function unescape(html) { // explicitly match decimal, hex, and named HTML entities return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { n = n.toLowerCase(); if (n === 'colon') return ':'; if (n.charAt(0) === '#') { return n.charAt(1) === 'x' ? String.fromCharCode(parseInt(n.substring(2), 16)) : String.fromCharCode(+n.substring(1)); } return ''; }); } export function edit(regex, opt) { regex = regex.source || regex; opt = opt || ''; return { replace: function(name, val) { val = val.source || val; val = val.replace(/(^|[^\[])\^/g, '$1'); regex = regex.replace(name, val); return this; }, getRegex: function() { return new RegExp(regex, opt); } }; } export function cleanUrl(sanitize, base, href) { if (sanitize) { let prot; try { prot = decodeURIComponent(unescape(href)) .replace(/[^\w:]/g, '') .toLowerCase(); } catch (e) { return null; } if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { return null; } } const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; if (base && !originIndependentUrl.test(href)) { href = resolveUrl(base, href); } try { href = encodeURI(href).replace(/%25/g, '%'); } catch (e) { return null; } return href; } export function resolveUrl(base, href) { if (!resolveUrl.baseUrls[' ' + base]) { // we can ignore everything in base after the last slash of its path component, // but we might need to add _that_ // https://tools.ietf.org/html/rfc3986#section-3 if (/^[^:]+:\/*[^/]*$/.test(base)) { resolveUrl.baseUrls[' ' + base] = base + '/'; } else { resolveUrl.baseUrls[' ' + base] = rtrim(base, '/', true); } } base = resolveUrl.baseUrls[' ' + base]; const relativeBase = base.indexOf(':') === -1; if (href.slice(0, 2) === '//') { if (relativeBase) { return href; } return base.replace(/^([^:]+:)[\s\S]*$/, '$1') + href; } else if (href.charAt(0) === '/') { if (relativeBase) { return href; } return base.replace(/^([^:]+:\/*[^/]*)[\s\S]*$/, '$1') + href; } else { return base + href; } } resolveUrl.baseUrls = {}; export function noop() {} noop.exec = noop; export function merge(obj) { let i = 1; let target; let key; for (; i < arguments.length; i++) { target = arguments[i]; for (key in target) { if (Object.prototype.hasOwnProperty.call(target, key)) { obj[key] = target[key]; } } } return obj; } export function splitCells(tableRow, count) { // ensure that every cell-delimiting pipe has a space // before it to distinguish it from an escaped pipe const row = tableRow.replace(/\|/g, function(match, offset, str) { let escaped = false; let curr = offset; while (--curr >= 0 && str[curr] === '\\') escaped = !escaped; if (escaped) { // odd number of slashes means | is escaped // so we leave it alone return '|'; } else { // add space before unescaped | return ' |'; } }); const cells = row.split(/ \|/); let i = 0; if (cells.length > count) { cells.splice(count); } else { while (cells.length < count) cells.push(''); } for (; i < cells.length; i++) { // leading or trailing whitespace is ignored per the gfm spec cells[i] = cells[i].trim().replace(/\\\|/g, '|'); } return cells; } // Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). // /c*$/ is vulnerable to REDOS. // invert: Remove suffix of non-c chars instead. Default falsey. export function rtrim(str, c, invert) { if (str.length === 0) { return ''; } // Length of suffix matching the invert condition. let suffLen = 0; // Step left until we fail to match the invert condition. while (suffLen < str.length) { const currChar = str.charAt(str.length - suffLen - 1); if (currChar === c && !invert) { suffLen++; } else if (currChar !== c && invert) { suffLen++; } else { break; } } return str.substr(0, str.length - suffLen); } export function findClosingBracket(str, b) { if (str.indexOf(b[1]) === -1) { return -1; } let level = 0; for (let i = 0; i < str.length; i++) { if (str[i] === '\\') { i++; } else if (str[i] === b[0]) { level++; } else if (str[i] === b[1]) { level--; if (level < 0) { return i; } } } return -1; } export function checkSanitizeDeprecation(opt) { if (opt && opt.sanitize && !opt.silent) { console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options'); } }