| const MarkdownIt = require('markdown-it'); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| function normalizeAllowedSchemes(allowedSchemes) { |
| if (!Array.isArray(allowedSchemes) || allowedSchemes.length === 0) { |
| return ['http', 'https', 'mailto', 'tel']; |
| } |
| return allowedSchemes |
| .map((s) => |
| String(s || '') |
| .trim() |
| .toLowerCase() |
| .replace(/:$/, '') |
| ) |
| .filter(Boolean); |
| } |
|
|
| function isRelativeUrl(url) { |
| const s = String(url || '').trim(); |
| return ( |
| s.startsWith('#') || |
| s.startsWith('/') || |
| s.startsWith('./') || |
| s.startsWith('../') || |
| s.startsWith('?') |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function sanitizeLinkHref(href, allowedSchemes) { |
| const raw = String(href || '').trim(); |
| if (!raw) return '#'; |
| if (isRelativeUrl(raw)) return raw; |
|
|
| |
| if (raw.startsWith('//')) return '#'; |
|
|
| try { |
| const parsed = new URL(raw); |
| const scheme = String(parsed.protocol || '') |
| .toLowerCase() |
| .replace(/:$/, ''); |
| return allowedSchemes.includes(scheme) ? raw : '#'; |
| } catch { |
| return '#'; |
| } |
| } |
|
|
| function createMarkdownIt({ allowedSchemes }) { |
| const md = new MarkdownIt({ |
| html: false, |
| linkify: true, |
| typographer: true, |
| }); |
|
|
| |
| |
| md.validateLink = () => true; |
|
|
| |
| md.disable('image'); |
|
|
| const normalizedSchemes = normalizeAllowedSchemes(allowedSchemes); |
| const defaultRender = md.renderer.rules.link_open; |
|
|
| md.renderer.rules.link_open = function (tokens, idx, options, env, self) { |
| const token = tokens[idx]; |
| const hrefIndex = token.attrIndex('href'); |
| if (hrefIndex >= 0) { |
| const originalHref = token.attrs[hrefIndex][1]; |
| token.attrs[hrefIndex][1] = sanitizeLinkHref(originalHref, normalizedSchemes); |
| } |
|
|
| return defaultRender |
| ? defaultRender(tokens, idx, options, env, self) |
| : self.renderToken(tokens, idx, options); |
| }; |
|
|
| return md; |
| } |
|
|
| |
| |
| |
| |
| |
| function renderMarkdownToHtml(markdownText, opts = {}) { |
| const md = createMarkdownIt({ allowedSchemes: opts.allowedSchemes }); |
| return md.render(String(markdownText || '')); |
| } |
|
|
| module.exports = { |
| sanitizeLinkHref, |
| renderMarkdownToHtml, |
| }; |
|
|