@juspay/svelte-ui-components 4.11.4 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  import { Marked } from 'marked';
2
+ import { parseFragment } from 'parse5';
2
3
  /**
3
4
  * Chat content comes from models and users, not from the app's own templates,
4
5
  * so the output must be safe without asking consumers to run a sanitizer:
5
- * raw HTML never passes through (it renders as escaped text), and only these
6
+ * raw HTML never passes through (escaped by default, tags optionally stripped), and only these
6
7
  * URL protocols survive on links. Everything else in the output is built by
7
8
  * marked from markdown syntax alone.
8
9
  */
@@ -109,10 +110,36 @@ function resolveTagAllowList(allowedTags) {
109
110
  function tagAllowed(allowed, tag) {
110
111
  return allowed === null || allowed.has(tag);
111
112
  }
112
- function createSanitizingRenderer(protocols, allowedTags, disableTaskLists) {
113
+ /**
114
+ * As with `narrow` and `resolveTagAllowList`, a plain-JS or web-component
115
+ * consumer can hand this anything at runtime, so anything other than the one
116
+ * recognised opt-in degrades to today's escaping rather than throwing — an
117
+ * unrecognised value must never be the reason content silently disappears.
118
+ */
119
+ function resolveRawHtmlMode(rawHtml) {
120
+ return rawHtml === 'strip' ? 'strip' : 'escape';
121
+ }
122
+ // An inert, server-compatible HTML parser handles quoted delimiters, entities
123
+ // and raw-text elements. Escaping its text nodes prevents decoded entities from
124
+ // becoming markup when the resulting string is rendered by Svelte.
125
+ function rawHtmlText(node) {
126
+ if ('value' in node) {
127
+ return escapeHtml(node.value);
128
+ }
129
+ // A <template>'s children live in a separate `content` fragment rather than
130
+ // in childNodes, so recursing only through childNodes loses its text.
131
+ if ('content' in node) {
132
+ return rawHtmlText(node.content);
133
+ }
134
+ if ('childNodes' in node) {
135
+ return node.childNodes.map(rawHtmlText).join('');
136
+ }
137
+ return '';
138
+ }
139
+ function createSanitizingRenderer(protocols, allowedTags, disableTaskLists, rawHtml) {
113
140
  return {
114
141
  html(token) {
115
- return escapeHtml(token.text);
142
+ return rawHtml === 'strip' ? rawHtmlText(parseFragment(token.text)) : escapeHtml(token.text);
116
143
  },
117
144
  link(token) {
118
145
  if (hasSafeProtocol(token.href, protocols.links) && tagAllowed(allowedTags, 'a')) {
@@ -233,19 +260,21 @@ function instanceFor(options) {
233
260
  const protocols = resolveProtocolSets(options.sanitize);
234
261
  const allowedTags = resolveTagAllowList(options.sanitize?.allowedTags);
235
262
  const disableTaskLists = options.sanitize?.disableTaskLists === true;
263
+ const rawHtml = resolveRawHtmlMode(options.sanitize?.rawHtml);
236
264
  const key = [
237
265
  breaks ? 'breaks' : 'default',
238
266
  [...protocols.links].sort().join(','),
239
267
  [...protocols.images].sort().join(','),
240
268
  allowedTags ? [...allowedTags].sort().join(',') : '*',
241
- disableTaskLists ? 'no-tasks' : 'tasks'
269
+ disableTaskLists ? 'no-tasks' : 'tasks',
270
+ rawHtml
242
271
  ].join('|');
243
272
  let instance = instances.get(key);
244
273
  if (!instance) {
245
274
  instance = new Marked({
246
275
  gfm: true,
247
276
  breaks,
248
- renderer: createSanitizingRenderer(protocols, allowedTags, disableTaskLists)
277
+ renderer: createSanitizingRenderer(protocols, allowedTags, disableTaskLists, rawHtml)
249
278
  });
250
279
  instances.set(key, instance);
251
280
  }
@@ -2,7 +2,7 @@ export type MarkdownTextProperties = OptionalMarkdownTextProperties & MandatoryM
2
2
  export type MandatoryMarkdownTextProperties = {
3
3
  /**
4
4
  * Markdown source. Rendered through the library's sanitized-by-construction
5
- * pipeline: raw HTML (block and inline) is escaped and shown as text, and
5
+ * pipeline: raw HTML is escaped by default (or stripped via sanitize.rawHtml), and
6
6
  * link/image URLs outside the safe-protocol allow-list are stripped while
7
7
  * their text is kept.
8
8
  */
@@ -34,8 +34,9 @@ export type OptionalMarkdownTextProperties = {
34
34
  * with the library's own allow-list for each surface, so it can only NARROW
35
35
  * what already renders, never widen it (listing `javascript:` here can never
36
36
  * resurrect it). Omitting `sanitize`, or `allowedProtocols`, keeps today's
37
- * defaults untouched. This is independent of the "raw HTML is always escaped"
38
- * guarantee, which has no opt-out.
37
+ * defaults untouched. This is independent of how raw HTML is disposed of,
38
+ * which `rawHtml` governs separately; raw HTML is never inserted as markup
39
+ * either way, so neither option can widen what renders.
39
40
  */
40
41
  export type MarkdownSanitizeOptions = {
41
42
  /**
@@ -58,9 +59,8 @@ export type MarkdownSanitizeOptions = {
58
59
  * `'blockquote'`, `'ul'`, `'ol'`, `'table'`, `'hr'`, and `'h1'`–`'h6'`. An
59
60
  * unrecognised name is simply ignored — it narrows nothing, it does not
60
61
  * error. Omitting `allowedTags` keeps today's defaults untouched: every tag
61
- * renders as it does now. This is independent of the "raw HTML is always
62
- * escaped" guarantee, which has no opt-out and is never affected by this
63
- * list.
62
+ * renders as it does now. This list governs markdown-GENERATED tags only and
63
+ * never affects raw HTML from the source, whose disposal is `rawHtml`'s job.
64
64
  */
65
65
  allowedTags?: string[];
66
66
  /**
@@ -71,15 +71,35 @@ export type MarkdownSanitizeOptions = {
71
71
  * safety one. Defaults to `false`, keeping today's checkbox rendering.
72
72
  */
73
73
  disableTaskLists?: boolean;
74
+ /**
75
+ * What to do with raw HTML typed literally into the markdown source, as
76
+ * opposed to the tags markdown itself generates (which `allowedTags`
77
+ * governs). Defaults to `'escape'` — today's behaviour, where
78
+ * `<script>alert(1)</script>` renders as visible escaped tag text.
79
+ *
80
+ * `'strip'` removes raw tags and comments while retaining their text:
81
+ * `<div>hello</div>` becomes `hello`. An inert HTML parser handles quoted
82
+ * delimiters and entities; every retained text node is escaped before output.
83
+ * Script/style contents remain visible text, never executable elements.
84
+ * Fenced code and ordinary angle-bracket prose are not raw HTML tokens and
85
+ * keep their existing rendering. Neither mode inserts source HTML into the DOM.
86
+ */
87
+ rawHtml?: MarkdownRawHtmlMode;
74
88
  };
89
+ /**
90
+ * `'escape'` renders raw HTML as visible escaped text (the default, and what
91
+ * the component has always done); `'strip'` removes it. See
92
+ * `MarkdownSanitizeOptions.rawHtml`.
93
+ */
94
+ export type MarkdownRawHtmlMode = 'escape' | 'strip';
75
95
  export type RenderMarkdownOptions = {
76
96
  /** Render single newlines as `<br>` (GFM "breaks" mode). */
77
97
  breaks?: boolean;
78
98
  /**
79
99
  * Parse as inline markdown: no block elements (`<p>`, lists, tables) are
80
100
  * produced, so the output can sit inside an existing `<p>` or `<span>`.
81
- * The same sanitization applies — inline raw HTML is escaped and unsafe
82
- * link/image protocols are stripped.
101
+ * The same sanitization applies — inline raw HTML is escaped (or dropped,
102
+ * per `sanitize.rawHtml`) and unsafe link/image protocols are stripped.
83
103
  */
84
104
  inline?: boolean;
85
105
  /** Accessible name for the scroll region wrapping each table. See `MarkdownTextProperties.tableLabel`. */