UNPKG

6.24 kBJavaScriptView Raw
1/**
2 * Helpers
3 */
4const escapeTest = /[&<>"']/;
5const escapeReplace = /[&<>"']/g;
6const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
7const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
8const escapeReplacements = {
9 '&': '&amp;',
10 '<': '&lt;',
11 '>': '&gt;',
12 '"': '&quot;',
13 "'": '&#39;'
14};
15const getEscapeReplacement = (ch) => escapeReplacements[ch];
16export function escape(html, encode) {
17 if (encode) {
18 if (escapeTest.test(html)) {
19 return html.replace(escapeReplace, getEscapeReplacement);
20 }
21 } else {
22 if (escapeTestNoEncode.test(html)) {
23 return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
24 }
25 }
26
27 return html;
28}
29
30const unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;
31
32export function unescape(html) {
33 // explicitly match decimal, hex, and named HTML entities
34 return html.replace(unescapeTest, (_, n) => {
35 n = n.toLowerCase();
36 if (n === 'colon') return ':';
37 if (n.charAt(0) === '#') {
38 return n.charAt(1) === 'x'
39 ? String.fromCharCode(parseInt(n.substring(2), 16))
40 : String.fromCharCode(+n.substring(1));
41 }
42 return '';
43 });
44}
45
46const caret = /(^|[^\[])\^/g;
47export function edit(regex, opt) {
48 regex = regex.source || regex;
49 opt = opt || '';
50 const obj = {
51 replace: (name, val) => {
52 val = val.source || val;
53 val = val.replace(caret, '$1');
54 regex = regex.replace(name, val);
55 return obj;
56 },
57 getRegex: () => {
58 return new RegExp(regex, opt);
59 }
60 };
61 return obj;
62}
63
64const nonWordAndColonTest = /[^\w:]/g;
65const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
66export function cleanUrl(sanitize, base, href) {
67 if (sanitize) {
68 let prot;
69 try {
70 prot = decodeURIComponent(unescape(href))
71 .replace(nonWordAndColonTest, '')
72 .toLowerCase();
73 } catch (e) {
74 return null;
75 }
76 if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
77 return null;
78 }
79 }
80 if (base && !originIndependentUrl.test(href)) {
81 href = resolveUrl(base, href);
82 }
83 try {
84 href = encodeURI(href).replace(/%25/g, '%');
85 } catch (e) {
86 return null;
87 }
88 return href;
89}
90
91const baseUrls = {};
92const justDomain = /^[^:]+:\/*[^/]*$/;
93const protocol = /^([^:]+:)[\s\S]*$/;
94const domain = /^([^:]+:\/*[^/]*)[\s\S]*$/;
95
96export function resolveUrl(base, href) {
97 if (!baseUrls[' ' + base]) {
98 // we can ignore everything in base after the last slash of its path component,
99 // but we might need to add _that_
100 // https://tools.ietf.org/html/rfc3986#section-3
101 if (justDomain.test(base)) {
102 baseUrls[' ' + base] = base + '/';
103 } else {
104 baseUrls[' ' + base] = rtrim(base, '/', true);
105 }
106 }
107 base = baseUrls[' ' + base];
108 const relativeBase = base.indexOf(':') === -1;
109
110 if (href.substring(0, 2) === '//') {
111 if (relativeBase) {
112 return href;
113 }
114 return base.replace(protocol, '$1') + href;
115 } else if (href.charAt(0) === '/') {
116 if (relativeBase) {
117 return href;
118 }
119 return base.replace(domain, '$1') + href;
120 } else {
121 return base + href;
122 }
123}
124
125export const noopTest = { exec: function noopTest() {} };
126
127export function merge(obj) {
128 let i = 1,
129 target,
130 key;
131
132 for (; i < arguments.length; i++) {
133 target = arguments[i];
134 for (key in target) {
135 if (Object.prototype.hasOwnProperty.call(target, key)) {
136 obj[key] = target[key];
137 }
138 }
139 }
140
141 return obj;
142}
143
144export function splitCells(tableRow, count) {
145 // ensure that every cell-delimiting pipe has a space
146 // before it to distinguish it from an escaped pipe
147 const row = tableRow.replace(/\|/g, (match, offset, str) => {
148 let escaped = false,
149 curr = offset;
150 while (--curr >= 0 && str[curr] === '\\') escaped = !escaped;
151 if (escaped) {
152 // odd number of slashes means | is escaped
153 // so we leave it alone
154 return '|';
155 } else {
156 // add space before unescaped |
157 return ' |';
158 }
159 }),
160 cells = row.split(/ \|/);
161 let i = 0;
162
163 // First/last cell in a row cannot be empty if it has no leading/trailing pipe
164 if (!cells[0].trim()) { cells.shift(); }
165 if (!cells[cells.length - 1].trim()) { cells.pop(); }
166
167 if (cells.length > count) {
168 cells.splice(count);
169 } else {
170 while (cells.length < count) cells.push('');
171 }
172
173 for (; i < cells.length; i++) {
174 // leading or trailing whitespace is ignored per the gfm spec
175 cells[i] = cells[i].trim().replace(/\\\|/g, '|');
176 }
177 return cells;
178}
179
180// Remove trailing 'c's. Equivalent to str.replace(/c*$/, '').
181// /c*$/ is vulnerable to REDOS.
182// invert: Remove suffix of non-c chars instead. Default falsey.
183export function rtrim(str, c, invert) {
184 const l = str.length;
185 if (l === 0) {
186 return '';
187 }
188
189 // Length of suffix matching the invert condition.
190 let suffLen = 0;
191
192 // Step left until we fail to match the invert condition.
193 while (suffLen < l) {
194 const currChar = str.charAt(l - suffLen - 1);
195 if (currChar === c && !invert) {
196 suffLen++;
197 } else if (currChar !== c && invert) {
198 suffLen++;
199 } else {
200 break;
201 }
202 }
203
204 return str.substr(0, l - suffLen);
205}
206
207export function findClosingBracket(str, b) {
208 if (str.indexOf(b[1]) === -1) {
209 return -1;
210 }
211 const l = str.length;
212 let level = 0,
213 i = 0;
214 for (; i < l; i++) {
215 if (str[i] === '\\') {
216 i++;
217 } else if (str[i] === b[0]) {
218 level++;
219 } else if (str[i] === b[1]) {
220 level--;
221 if (level < 0) {
222 return i;
223 }
224 }
225 }
226 return -1;
227}
228
229export function checkSanitizeDeprecation(opt) {
230 if (opt && opt.sanitize && !opt.silent) {
231 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');
232 }
233}
234
235// copied from https://stackoverflow.com/a/5450113/806777
236export function repeatString(pattern, count) {
237 if (count < 1) {
238 return '';
239 }
240 let result = '';
241 while (count > 1) {
242 if (count & 1) {
243 result += pattern;
244 }
245 count >>= 1;
246 pattern += pattern;
247 }
248 return result + pattern;
249}