@superbuilders/incept-renderer 0.1.13 → 0.1.15

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.
@@ -7,214 +7,9 @@ import * as logger2 from '@superbuilders/slog';
7
7
  // src/parser.ts
8
8
 
9
9
  // src/html/sanitize.ts
10
- var DEFAULT_CONFIG = {
11
- // HTML content tags
12
- allowedTags: /* @__PURE__ */ new Set([
13
- // Text content
14
- "p",
15
- "span",
16
- "div",
17
- "br",
18
- "hr",
19
- // Headings
20
- "h1",
21
- "h2",
22
- "h3",
23
- "h4",
24
- "h5",
25
- "h6",
26
- // Formatting
27
- "b",
28
- "i",
29
- "u",
30
- "strong",
31
- "em",
32
- "mark",
33
- "small",
34
- "sub",
35
- "sup",
36
- "code",
37
- "pre",
38
- "kbd",
39
- // Lists
40
- "ul",
41
- "ol",
42
- "li",
43
- "dl",
44
- "dt",
45
- "dd",
46
- // Tables
47
- "table",
48
- "thead",
49
- "tbody",
50
- "tfoot",
51
- "tr",
52
- "th",
53
- "td",
54
- "caption",
55
- "colgroup",
56
- "col",
57
- // Media
58
- "img",
59
- "audio",
60
- "video",
61
- "source",
62
- "track",
63
- // Semantic
64
- "article",
65
- "section",
66
- "nav",
67
- "aside",
68
- "header",
69
- "footer",
70
- "main",
71
- "figure",
72
- "figcaption",
73
- "blockquote",
74
- "cite",
75
- // Interactive
76
- "details",
77
- "summary",
78
- // Links
79
- "a",
80
- // Forms (for future interactive elements)
81
- "label",
82
- "button"
83
- ]),
84
- allowedAttributes: {
85
- // Global attributes
86
- "*": /* @__PURE__ */ new Set(["class", "id", "lang", "dir", "title", "style"]),
87
- // Specific attributes
88
- img: /* @__PURE__ */ new Set(["src", "alt", "width", "height", "style"]),
89
- a: /* @__PURE__ */ new Set(["href", "target", "rel"]),
90
- audio: /* @__PURE__ */ new Set(["src", "controls", "loop", "muted"]),
91
- video: /* @__PURE__ */ new Set(["src", "controls", "loop", "muted", "width", "height", "poster"]),
92
- source: /* @__PURE__ */ new Set(["src", "type"]),
93
- track: /* @__PURE__ */ new Set(["src", "kind", "srclang", "label"])
94
- },
95
- allowDataAttributes: false,
96
- allowMathML: true
97
- };
98
- var MATHML_TAGS = /* @__PURE__ */ new Set([
99
- // Root
100
- "math",
101
- // Token elements
102
- "mi",
103
- "mn",
104
- "mo",
105
- "mtext",
106
- "mspace",
107
- "ms",
108
- // Layout
109
- "mrow",
110
- "mfrac",
111
- "msqrt",
112
- "mroot",
113
- "mstyle",
114
- "merror",
115
- "mpadded",
116
- "mphantom",
117
- "mfenced",
118
- "menclose",
119
- // Scripts and limits
120
- "msub",
121
- "msup",
122
- "msubsup",
123
- "munder",
124
- "mover",
125
- "munderover",
126
- "mmultiscripts",
127
- "mprescripts",
128
- "none",
129
- // Tables
130
- "mtable",
131
- "mtr",
132
- "mtd",
133
- "maligngroup",
134
- "malignmark",
135
- // Elementary math
136
- "mstack",
137
- "mlongdiv",
138
- "msgroup",
139
- "msrow",
140
- "mscarries",
141
- "mscarry",
142
- "msline",
143
- // Semantic
144
- "semantics",
145
- "annotation",
146
- "annotation-xml"
147
- ]);
148
- function sanitizeHtml(html, config = {}) {
149
- const cfg = { ...DEFAULT_CONFIG, ...config };
150
- const dangerousPatterns = [
151
- /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
152
- /javascript:/gi,
153
- /on\w+\s*=/gi,
154
- // Event handlers
155
- /<iframe\b/gi,
156
- /<embed\b/gi,
157
- /<object\b/gi,
158
- /data:text\/html/gi
159
- ];
160
- let sanitized = html;
161
- for (const pattern of dangerousPatterns) {
162
- sanitized = sanitized.replace(pattern, "");
163
- }
164
- const cleaned = cleanHtml(sanitized, cfg);
165
- return cleaned;
166
- }
167
- function cleanHtml(html, config) {
168
- let cleaned = html;
169
- const tagPattern = /<\/?([a-zA-Z][a-zA-Z0-9-]*)([^>]*)>/g;
170
- cleaned = cleaned.replace(tagPattern, (match, tagName, _attrs) => {
171
- const tag = tagName.toLowerCase();
172
- const isMathML = tag === "math" || cleaned.includes("<math");
173
- if (isMathML && config.allowMathML && MATHML_TAGS.has(tag)) {
174
- return match;
175
- }
176
- if (config.allowedTags.has(tag)) {
177
- return cleanAttributesString(match, tag, config);
178
- }
179
- return "";
180
- });
181
- return cleaned;
182
- }
183
- function cleanAttributesString(tagString, tagName, config) {
184
- const attrPattern = /\s+([a-zA-Z][a-zA-Z0-9-:]*)(?:="([^"]*)"|'([^']*)'|=([^\s>]+)|(?=\s|>))/g;
185
- let cleanedTag = `<${tagString.startsWith("</") ? "/" : ""}${tagName}`;
186
- let match = attrPattern.exec(tagString);
187
- while (match !== null) {
188
- const attrName = (match[1] || "").toLowerCase();
189
- const attrValue = match[2] || match[3] || match[4] || "";
190
- const globalAttrs = config.allowedAttributes["*"] || /* @__PURE__ */ new Set();
191
- const tagAttrs = config.allowedAttributes[tagName] || /* @__PURE__ */ new Set();
192
- let isAllowed = globalAttrs.has(attrName) || tagAttrs.has(attrName);
193
- if (tagName === "img" && (attrName === "width" || attrName === "height" || attrName === "style")) {
194
- isAllowed = false;
195
- }
196
- if (!isAllowed && config.allowDataAttributes && attrName.startsWith("data-")) {
197
- isAllowed = true;
198
- }
199
- if (isAllowed && !isDangerousAttributeValue(attrName, attrValue)) {
200
- cleanedTag += ` ${attrName}="${attrValue.replace(/"/g, "&quot;")}"`;
201
- }
202
- match = attrPattern.exec(tagString);
203
- }
204
- cleanedTag += ">";
205
- return cleanedTag;
206
- }
207
- function isDangerousAttributeValue(name, value) {
208
- const valueLower = value.toLowerCase().trim();
209
- if (name === "href" || name === "src") {
210
- if (valueLower.startsWith("javascript:") || valueLower.startsWith("data:text/html")) {
211
- return true;
212
- }
213
- }
214
- if (valueLower.includes("javascript:") || valueLower.includes("onerror=")) {
215
- return true;
216
- }
217
- return false;
10
+ function sanitizeHtml(html) {
11
+ if (!html) return html;
12
+ return html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/\s+on\w+\s*=\s*["'][^"']*["']/gi, "").replace(/\s+on\w+\s*=\s*[^\s>]+/gi, "").replace(/\b(href|src)\s*=\s*["']?\s*javascript:[^"'>\s]*/gi, "").replace(/\b(href|src)\s*=\s*["']?\s*data:text\/html[^"'>\s]*/gi, "").replace(/<(iframe|embed|object)\b[^>]*>[\s\S]*?<\/\1>/gi, "").replace(/<(iframe|embed|object)\b[^>]*\/?>/gi, "");
218
13
  }
219
14
 
220
15
  // src/html/serialize.ts
@@ -1300,7 +1095,7 @@ function buildDisplayModelFromXml(qtiXml) {
1300
1095
  return { type: "stimulus", html: sanitizeHtml(block.html) };
1301
1096
  }
1302
1097
  if (block.type === "richStimulus") {
1303
- const html = sanitizeHtml(block.html, { allowDataAttributes: true });
1098
+ const html = sanitizeHtml(block.html);
1304
1099
  const inlineEmbeds = {};
1305
1100
  for (const [key, embed] of Object.entries(block.inlineEmbeds)) {
1306
1101
  inlineEmbeds[key] = {
@@ -1399,7 +1194,7 @@ function buildDisplayModelFromXml(qtiXml) {
1399
1194
  responseId: interaction.responseIdentifier,
1400
1195
  gapTexts,
1401
1196
  gaps,
1402
- contentHtml: sanitizeHtml(interaction.contentHtml, { allowDataAttributes: true })
1197
+ contentHtml: sanitizeHtml(interaction.contentHtml)
1403
1198
  }
1404
1199
  };
1405
1200
  }