123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- /**
- * Helpers
- */
- const escapeTest = /[&<>"']/;
- const escapeReplace = /[&<>"']/g;
- const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
- const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
- const escapeReplacements = {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- "'": '''
- };
- const getEscapeReplacement = (ch) => escapeReplacements[ch];
- export function escape(html, encode) {
- if (encode) {
- if (escapeTest.test(html)) {
- return html.replace(escapeReplace, getEscapeReplacement);
- }
- } else {
- if (escapeTestNoEncode.test(html)) {
- return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
- }
- }
- return html;
- }
- const unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;
- export function unescape(html) {
- // explicitly match decimal, hex, and named HTML entities
- return html.replace(unescapeTest, (_, 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 '';
- });
- }
- const caret = /(^|[^\[])\^/g;
- export function edit(regex, opt) {
- regex = regex.source || regex;
- opt = opt || '';
- const obj = {
- replace: (name, val) => {
- val = val.source || val;
- val = val.replace(caret, '$1');
- regex = regex.replace(name, val);
- return obj;
- },
- getRegex: () => {
- return new RegExp(regex, opt);
- }
- };
- return obj;
- }
- const nonWordAndColonTest = /[^\w:]/g;
- const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
- export function cleanUrl(sanitize, base, href) {
- if (sanitize) {
- let prot;
- try {
- prot = decodeURIComponent(unescape(href))
- .replace(nonWordAndColonTest, '')
- .toLowerCase();
- } catch (e) {
- return null;
- }
- if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
- return null;
- }
- }
- if (base && !originIndependentUrl.test(href)) {
- href = resolveUrl(base, href);
- }
- try {
- href = encodeURI(href).replace(/%25/g, '%');
- } catch (e) {
- return null;
- }
- return href;
- }
- const baseUrls = {};
- const justDomain = /^[^:]+:\/*[^/]*$/;
- const protocol = /^([^:]+:)[\s\S]*$/;
- const domain = /^([^:]+:\/*[^/]*)[\s\S]*$/;
- export function resolveUrl(base, href) {
- if (!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 (justDomain.test(base)) {
- baseUrls[' ' + base] = base + '/';
- } else {
- baseUrls[' ' + base] = rtrim(base, '/', true);
- }
- }
- base = baseUrls[' ' + base];
- const relativeBase = base.indexOf(':') === -1;
- if (href.substring(0, 2) === '//') {
- if (relativeBase) {
- return href;
- }
- return base.replace(protocol, '$1') + href;
- } else if (href.charAt(0) === '/') {
- if (relativeBase) {
- return href;
- }
- return base.replace(domain, '$1') + href;
- } else {
- return base + href;
- }
- }
- export const noopTest = { exec: function noopTest() {} };
- export function merge(obj) {
- let i = 1,
- target,
- 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, (match, offset, str) => {
- let escaped = false,
- 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 ' |';
- }
- }),
- cells = row.split(/ \|/);
- let i = 0;
- // First/last cell in a row cannot be empty if it has no leading/trailing pipe
- if (!cells[0].trim()) { cells.shift(); }
- if (!cells[cells.length - 1].trim()) { cells.pop(); }
- 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) {
- const l = str.length;
- if (l === 0) {
- return '';
- }
- // Length of suffix matching the invert condition.
- let suffLen = 0;
- // Step left until we fail to match the invert condition.
- while (suffLen < l) {
- const currChar = str.charAt(l - suffLen - 1);
- if (currChar === c && !invert) {
- suffLen++;
- } else if (currChar !== c && invert) {
- suffLen++;
- } else {
- break;
- }
- }
- return str.substr(0, l - suffLen);
- }
- export function findClosingBracket(str, b) {
- if (str.indexOf(b[1]) === -1) {
- return -1;
- }
- const l = str.length;
- let level = 0,
- i = 0;
- for (; i < l; 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');
- }
- }
- // copied from https://stackoverflow.com/a/5450113/806777
- export function repeatString(pattern, count) {
- if (count < 1) {
- return '';
- }
- let result = '';
- while (count > 1) {
- if (count & 1) {
- result += pattern;
- }
- count >>= 1;
- pattern += pattern;
- }
- return result + pattern;
- }
|