@stacksjs/sanitizer 0.2.198 → 0.2.199

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.
Files changed (2) hide show
  1. package/dist/index.js +1 -465
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,466 +1,2 @@
1
1
  // @bun
2
- // src/presets.ts
3
- var strict = {
4
- allowedTags: [
5
- "p",
6
- "br",
7
- "strong",
8
- "em",
9
- "u",
10
- "span",
11
- "h1",
12
- "h2",
13
- "h3",
14
- "h4",
15
- "h5",
16
- "h6",
17
- "ul",
18
- "ol",
19
- "li",
20
- "code",
21
- "pre"
22
- ],
23
- allowedAttributes: {
24
- "*": ["class", "id"]
25
- },
26
- allowedSchemes: ["http", "https", "mailto"],
27
- allowDataAttributes: false,
28
- allowAriaAttributes: true,
29
- stripTags: true,
30
- allowComments: false
31
- };
32
- var basic = {
33
- allowedTags: [
34
- "p",
35
- "br",
36
- "strong",
37
- "em",
38
- "u",
39
- "span",
40
- "div",
41
- "h1",
42
- "h2",
43
- "h3",
44
- "h4",
45
- "h5",
46
- "h6",
47
- "ul",
48
- "ol",
49
- "li",
50
- "a",
51
- "img",
52
- "code",
53
- "pre",
54
- "blockquote",
55
- "table",
56
- "thead",
57
- "tbody",
58
- "tr",
59
- "th",
60
- "td"
61
- ],
62
- allowedAttributes: {
63
- "*": ["class", "id"],
64
- a: ["class", "id", "href", "title", "target", "rel"],
65
- img: ["class", "id", "src", "alt", "title", "width", "height"],
66
- td: ["class", "id", "colspan", "rowspan", "align"],
67
- th: ["class", "id", "colspan", "rowspan", "align", "scope"]
68
- },
69
- allowedSchemes: ["http", "https", "mailto", "tel"],
70
- allowDataAttributes: false,
71
- allowAriaAttributes: true,
72
- stripTags: true,
73
- allowComments: false
74
- };
75
- var relaxed = {
76
- allowedTags: [
77
- "p",
78
- "br",
79
- "strong",
80
- "em",
81
- "u",
82
- "span",
83
- "div",
84
- "section",
85
- "article",
86
- "aside",
87
- "h1",
88
- "h2",
89
- "h3",
90
- "h4",
91
- "h5",
92
- "h6",
93
- "ul",
94
- "ol",
95
- "li",
96
- "dl",
97
- "dt",
98
- "dd",
99
- "a",
100
- "img",
101
- "figure",
102
- "figcaption",
103
- "code",
104
- "pre",
105
- "kbd",
106
- "samp",
107
- "var",
108
- "blockquote",
109
- "q",
110
- "cite",
111
- "table",
112
- "thead",
113
- "tbody",
114
- "tfoot",
115
- "tr",
116
- "th",
117
- "td",
118
- "caption",
119
- "del",
120
- "ins",
121
- "sub",
122
- "sup",
123
- "abbr",
124
- "address",
125
- "time",
126
- "hr",
127
- "video",
128
- "audio",
129
- "source",
130
- "track"
131
- ],
132
- allowedAttributes: {
133
- "*": ["class", "id", "title"],
134
- a: ["class", "id", "href", "title", "target", "rel"],
135
- img: ["class", "id", "src", "srcset", "alt", "title", "width", "height", "loading"],
136
- video: ["class", "id", "src", "width", "height", "controls", "preload", "poster"],
137
- audio: ["class", "id", "src", "controls", "preload"],
138
- source: ["src", "type"],
139
- track: ["src", "kind", "srclang", "label"],
140
- td: ["class", "id", "colspan", "rowspan", "align"],
141
- th: ["class", "id", "colspan", "rowspan", "align", "scope"],
142
- time: ["class", "id", "datetime"],
143
- abbr: ["class", "id", "title"]
144
- },
145
- allowedSchemes: ["http", "https", "mailto", "tel", "data"],
146
- allowDataAttributes: true,
147
- allowAriaAttributes: true,
148
- stripTags: true,
149
- allowComments: false
150
- };
151
- var markdown = {
152
- allowedTags: [
153
- "p",
154
- "br",
155
- "strong",
156
- "em",
157
- "span",
158
- "h1",
159
- "h2",
160
- "h3",
161
- "h4",
162
- "h5",
163
- "h6",
164
- "ul",
165
- "ol",
166
- "li",
167
- "a",
168
- "img",
169
- "code",
170
- "pre",
171
- "blockquote",
172
- "table",
173
- "thead",
174
- "tbody",
175
- "tr",
176
- "th",
177
- "td",
178
- "del",
179
- "ins",
180
- "hr",
181
- "input"
182
- ],
183
- allowedAttributes: {
184
- "*": ["class", "id"],
185
- a: ["class", "id", "href", "title"],
186
- img: ["class", "id", "src", "alt", "title"],
187
- code: ["class"],
188
- td: ["align"],
189
- th: ["align", "scope"],
190
- input: ["type", "checked", "disabled"]
191
- },
192
- allowedSchemes: ["http", "https", "mailto"],
193
- allowDataAttributes: false,
194
- allowAriaAttributes: false,
195
- stripTags: true,
196
- allowComments: false
197
- };
198
- function getPreset(name) {
199
- switch (name) {
200
- case "strict":
201
- return strict;
202
- case "basic":
203
- return basic;
204
- case "relaxed":
205
- return relaxed;
206
- case "markdown":
207
- return markdown;
208
- default:
209
- return basic;
210
- }
211
- }
212
-
213
- // src/sanitizer.ts
214
- var DEFAULT_OPTIONS = {
215
- allowedTags: [],
216
- allowedAttributes: {},
217
- allowedSchemes: ["http", "https", "mailto"],
218
- allowDataAttributes: false,
219
- allowAriaAttributes: true,
220
- stripTags: true,
221
- allowComments: false
222
- };
223
- var DANGEROUS_TAGS = [
224
- "script",
225
- "iframe",
226
- "object",
227
- "embed",
228
- "applet",
229
- "base",
230
- "link",
231
- "meta",
232
- "style"
233
- ];
234
- var URL_ATTRIBUTES = [
235
- "href",
236
- "src",
237
- "action",
238
- "formaction",
239
- "data",
240
- "poster",
241
- "cite",
242
- "background",
243
- "longdesc"
244
- ];
245
- var EVENT_ATTRIBUTES_REGEX = /^on\w+/i;
246
- function sanitize(html, options) {
247
- const result = sanitizeWithInfo(html, options);
248
- return result.html;
249
- }
250
- function sanitizeWithInfo(html, options) {
251
- const opts = typeof options === "string" ? getPreset(options) : { ...DEFAULT_OPTIONS, ...options };
252
- const removedTags = [];
253
- const removedAttributes = [];
254
- let result = html;
255
- let modified = false;
256
- if (!opts.allowComments) {
257
- const commentRegex = /<!--[\s\S]*?-->/g;
258
- if (commentRegex.test(result)) {
259
- result = result.replace(commentRegex, "");
260
- modified = true;
261
- }
262
- }
263
- const tagRegex = /<(\/?)([a-zA-Z][\w-]*)([^>]*)>/g;
264
- result = result.replace(tagRegex, (match, closing, tagName, attributesStr) => {
265
- const lowerTag = tagName.toLowerCase();
266
- if (DANGEROUS_TAGS.includes(lowerTag)) {
267
- if (!removedTags.includes(lowerTag)) {
268
- removedTags.push(lowerTag);
269
- }
270
- modified = true;
271
- return opts.stripTags ? "" : escapeHtml(match);
272
- }
273
- const allowedTags = opts.allowedTags || [];
274
- if (allowedTags.length > 0 && !allowedTags.includes(lowerTag)) {
275
- if (!removedTags.includes(lowerTag)) {
276
- removedTags.push(lowerTag);
277
- }
278
- modified = true;
279
- return opts.stripTags ? "" : escapeHtml(match);
280
- }
281
- if (closing) {
282
- return match;
283
- }
284
- const sanitizedAttrs = sanitizeAttributes(attributesStr, lowerTag, opts, removedAttributes);
285
- if (sanitizedAttrs !== attributesStr) {
286
- modified = true;
287
- }
288
- if (opts.transformTag) {
289
- const attributes = parseAttributes(sanitizedAttrs);
290
- const transformed = opts.transformTag(lowerTag, attributes);
291
- if (transformed === null) {
292
- if (!removedTags.includes(lowerTag)) {
293
- removedTags.push(lowerTag);
294
- }
295
- modified = true;
296
- return "";
297
- }
298
- if (transformed.tagName !== lowerTag || JSON.stringify(transformed.attributes) !== JSON.stringify(attributes)) {
299
- modified = true;
300
- return buildTag(transformed.tagName, transformed.attributes);
301
- }
302
- }
303
- return `<${tagName}${sanitizedAttrs}>`;
304
- });
305
- return {
306
- html: result,
307
- modified,
308
- removedTags: removedTags.length > 0 ? removedTags : undefined,
309
- removedAttributes: removedAttributes.length > 0 ? removedAttributes : undefined
310
- };
311
- }
312
- function sanitizeAttributes(attributesStr, tagName, options, removedAttributes) {
313
- if (!attributesStr.trim()) {
314
- return attributesStr;
315
- }
316
- const attributes = parseAttributes(attributesStr);
317
- const sanitized = {};
318
- for (const [name, value] of Object.entries(attributes)) {
319
- const lowerName = name.toLowerCase();
320
- if (EVENT_ATTRIBUTES_REGEX.test(lowerName)) {
321
- if (!removedAttributes.includes(lowerName)) {
322
- removedAttributes.push(lowerName);
323
- }
324
- continue;
325
- }
326
- if (!isAttributeAllowed(lowerName, tagName, options)) {
327
- if (!removedAttributes.includes(lowerName)) {
328
- removedAttributes.push(lowerName);
329
- }
330
- continue;
331
- }
332
- if (URL_ATTRIBUTES.includes(lowerName)) {
333
- if (!isUrlSafe(value, options)) {
334
- if (!removedAttributes.includes(lowerName)) {
335
- removedAttributes.push(lowerName);
336
- }
337
- continue;
338
- }
339
- }
340
- if (lowerName === "style" && options.allowedStyles) {
341
- sanitized[name] = sanitizeStyle(value, options.allowedStyles);
342
- } else {
343
- sanitized[name] = value;
344
- }
345
- }
346
- return buildAttributes(sanitized);
347
- }
348
- function isAttributeAllowed(attrName, tagName, options) {
349
- const allowedAttrs = options.allowedAttributes;
350
- if (!allowedAttrs) {
351
- return false;
352
- }
353
- if (attrName.startsWith("data-")) {
354
- return options.allowDataAttributes || false;
355
- }
356
- if (attrName.startsWith("aria-")) {
357
- return options.allowAriaAttributes || false;
358
- }
359
- if (Array.isArray(allowedAttrs)) {
360
- return allowedAttrs.includes(attrName);
361
- }
362
- if (allowedAttrs[tagName]?.includes(attrName)) {
363
- return true;
364
- }
365
- if (allowedAttrs["*"]?.includes(attrName)) {
366
- return true;
367
- }
368
- return false;
369
- }
370
- function isUrlSafe(url, options) {
371
- if (options.urlValidator) {
372
- return options.urlValidator(url);
373
- }
374
- const trimmed = url.trim().toLowerCase();
375
- const cleaned = trimmed.replace(/[\s\x00-\x1F]/g, "");
376
- const dangerousProtocols = ["javascript:", "data:text/html", "vbscript:", "file:"];
377
- for (const protocol of dangerousProtocols) {
378
- if (cleaned.startsWith(protocol)) {
379
- return false;
380
- }
381
- }
382
- if (options.allowedSchemes && options.allowedSchemes.length > 0) {
383
- if (!cleaned.includes(":")) {
384
- return true;
385
- }
386
- const hasAllowedScheme = options.allowedSchemes.some((scheme) => {
387
- return cleaned.startsWith(`${scheme}:`);
388
- });
389
- if (!hasAllowedScheme) {
390
- return false;
391
- }
392
- }
393
- return true;
394
- }
395
- function sanitizeStyle(style, allowedStyles) {
396
- const properties = style.split(";").filter((p) => p.trim());
397
- const sanitized = [];
398
- for (const prop of properties) {
399
- const colonIndex = prop.indexOf(":");
400
- if (colonIndex === -1)
401
- continue;
402
- const name = prop.substring(0, colonIndex).trim().toLowerCase();
403
- const value = prop.substring(colonIndex + 1).trim();
404
- if (allowedStyles.includes(name)) {
405
- if (!value.toLowerCase().includes("javascript:") && !value.toLowerCase().includes("expression(")) {
406
- sanitized.push(`${name}: ${value}`);
407
- }
408
- }
409
- }
410
- return sanitized.join("; ");
411
- }
412
- function parseAttributes(attributesStr) {
413
- const attributes = {};
414
- const attrRegex = /([\w-]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
415
- let match;
416
- while (match = attrRegex.exec(attributesStr)) {
417
- const name = match[1];
418
- const value = match[2] || match[3] || match[4] || "";
419
- attributes[name] = value;
420
- }
421
- return attributes;
422
- }
423
- function buildAttributes(attributes) {
424
- const parts = [];
425
- for (const [name, value] of Object.entries(attributes)) {
426
- if (value === "") {
427
- parts.push(name);
428
- } else {
429
- const escaped = value.replace(/"/g, "&quot;");
430
- parts.push(`${name}="${escaped}"`);
431
- }
432
- }
433
- return parts.length > 0 ? ` ${parts.join(" ")}` : "";
434
- }
435
- function buildTag(tagName, attributes) {
436
- return `<${tagName}${buildAttributes(attributes)}>`;
437
- }
438
- function escapeHtml(text) {
439
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
440
- }
441
- function isSafe(html, options) {
442
- const result = sanitizeWithInfo(html, options);
443
- return !result.modified;
444
- }
445
- function stripTags(html) {
446
- return html.replace(/<[^>]*>/g, "");
447
- }
448
- function escape(text) {
449
- return escapeHtml(text);
450
- }
451
-
452
- // src/index.ts
453
- var src_default = sanitize;
454
- export {
455
- stripTags,
456
- strict,
457
- sanitizeWithInfo,
458
- sanitize,
459
- relaxed,
460
- markdown,
461
- isSafe,
462
- getPreset,
463
- escape,
464
- src_default as default,
465
- basic
466
- };
2
+ var L={allowedTags:["p","br","strong","em","u","span","h1","h2","h3","h4","h5","h6","ul","ol","li","code","pre"],allowedAttributes:{"*":["class","id"]},allowedSchemes:["http","https","mailto"],allowDataAttributes:!1,allowAriaAttributes:!0,stripTags:!0,allowComments:!1},B={allowedTags:["p","br","strong","em","u","span","div","h1","h2","h3","h4","h5","h6","ul","ol","li","a","img","code","pre","blockquote","table","thead","tbody","tr","th","td"],allowedAttributes:{"*":["class","id"],a:["class","id","href","title","target","rel"],img:["class","id","src","alt","title","width","height"],td:["class","id","colspan","rowspan","align"],th:["class","id","colspan","rowspan","align","scope"]},allowedSchemes:["http","https","mailto","tel"],allowDataAttributes:!1,allowAriaAttributes:!0,stripTags:!0,allowComments:!1},x={allowedTags:["p","br","strong","em","u","span","div","section","article","aside","h1","h2","h3","h4","h5","h6","ul","ol","li","dl","dt","dd","a","img","figure","figcaption","code","pre","kbd","samp","var","blockquote","q","cite","table","thead","tbody","tfoot","tr","th","td","caption","del","ins","sub","sup","abbr","address","time","hr","video","audio","source","track"],allowedAttributes:{"*":["class","id","title"],a:["class","id","href","title","target","rel"],img:["class","id","src","srcset","alt","title","width","height","loading"],video:["class","id","src","width","height","controls","preload","poster"],audio:["class","id","src","controls","preload"],source:["src","type"],track:["src","kind","srclang","label"],td:["class","id","colspan","rowspan","align"],th:["class","id","colspan","rowspan","align","scope"],time:["class","id","datetime"],abbr:["class","id","title"]},allowedSchemes:["http","https","mailto","tel","data"],allowDataAttributes:!0,allowAriaAttributes:!0,stripTags:!0,allowComments:!1},_={allowedTags:["p","br","strong","em","span","h1","h2","h3","h4","h5","h6","ul","ol","li","a","img","code","pre","blockquote","table","thead","tbody","tr","th","td","del","ins","hr","input"],allowedAttributes:{"*":["class","id"],a:["class","id","href","title"],img:["class","id","src","alt","title"],code:["class"],td:["align"],th:["align","scope"],input:["type","checked","disabled"]},allowedSchemes:["http","https","mailto"],allowDataAttributes:!1,allowAriaAttributes:!1,stripTags:!0,allowComments:!1};function M(q){switch(q){case"strict":return L;case"basic":return B;case"relaxed":return x;case"markdown":return _;default:return B}}var I={allowedTags:[],allowedAttributes:{},allowedSchemes:["http","https","mailto"],allowDataAttributes:!1,allowAriaAttributes:!0,stripTags:!0,allowComments:!1},z=["script","iframe","object","embed","applet","base","link","meta","style"],E=["href","src","action","formaction","data","poster","cite","background","longdesc"],R=/^on\w+/i;function W(q,C){return k(q,C).html}function k(q,C){let D=typeof C==="string"?M(C):{...I,...C},j=[],K=[],J=q,Q=!1;if(!D.allowComments){let P=/<!--[\s\S]*?-->/g;if(P.test(J))J=J.replace(P,""),Q=!0}let Y=/<(\/?)([a-zA-Z][\w-]*)([^>]*)>/g;return J=J.replace(Y,(P,y,O,F)=>{let X=O.toLowerCase();if(z.includes(X)){if(!j.includes(X))j.push(X);return Q=!0,D.stripTags?"":V(P)}let G=D.allowedTags||[];if(G.length>0&&!G.includes(X)){if(!j.includes(X))j.push(X);return Q=!0,D.stripTags?"":V(P)}if(y)return P;let $=S(F,X,D,K);if($!==F)Q=!0;if(D.transformTag){let H=f($),Z=D.transformTag(X,H);if(Z===null){if(!j.includes(X))j.push(X);return Q=!0,""}if(Z.tagName!==X||JSON.stringify(Z.attributes)!==JSON.stringify(H))return Q=!0,v(Z.tagName,Z.attributes)}return`<${O}${$}>`}),{html:J,modified:Q,removedTags:j.length>0?j:void 0,removedAttributes:K.length>0?K:void 0}}function S(q,C,D,j){if(!q.trim())return q;let K=f(q),J={};for(let[Q,Y]of Object.entries(K)){let P=Q.toLowerCase();if(R.test(P)){if(!j.includes(P))j.push(P);continue}if(!h(P,C,D)){if(!j.includes(P))j.push(P);continue}if(E.includes(P)){if(!c(Y,D)){if(!j.includes(P))j.push(P);continue}}if(P==="style"&&D.allowedStyles)J[Q]=w(Y,D.allowedStyles);else J[Q]=Y}return U(J)}function h(q,C,D){let j=D.allowedAttributes;if(!j)return!1;if(q.startsWith("data-"))return D.allowDataAttributes||!1;if(q.startsWith("aria-"))return D.allowAriaAttributes||!1;if(Array.isArray(j))return j.includes(q);if(j[C]?.includes(q))return!0;if(j["*"]?.includes(q))return!0;return!1}function c(q,C){if(C.urlValidator)return C.urlValidator(q);let j=q.trim().toLowerCase().replace(/[\s\x00-\x1F]/g,""),K=["javascript:","data:text/html","vbscript:","file:"];for(let J of K)if(j.startsWith(J))return!1;if(C.allowedSchemes&&C.allowedSchemes.length>0){if(!j.includes(":"))return!0;if(!C.allowedSchemes.some((Q)=>{return j.startsWith(`${Q}:`)}))return!1}return!0}function w(q,C){let D=q.split(";").filter((K)=>K.trim()),j=[];for(let K of D){let J=K.indexOf(":");if(J===-1)continue;let Q=K.substring(0,J).trim().toLowerCase(),Y=K.substring(J+1).trim();if(C.includes(Q)){if(!Y.toLowerCase().includes("javascript:")&&!Y.toLowerCase().includes("expression("))j.push(`${Q}: ${Y}`)}}return j.join("; ")}function f(q){let C={},D=/([\w-]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g,j;while(j=D.exec(q)){let K=j[1],J=j[2]||j[3]||j[4]||"";C[K]=J}return C}function U(q){let C=[];for(let[D,j]of Object.entries(q))if(j==="")C.push(D);else{let K=j.replace(/"/g,"&quot;");C.push(`${D}="${K}"`)}return C.length>0?` ${C.join(" ")}`:""}function v(q,C){return`<${q}${U(C)}>`}function V(q){return q.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function T(q,C){return!k(q,C).modified}function N(q){return q.replace(/<[^>]*>/g,"")}function A(q){return V(q)}var s=W;export{N as stripTags,L as strict,k as sanitizeWithInfo,W as sanitize,x as relaxed,_ as markdown,T as isSafe,M as getPreset,A as escape,s as default,B as basic};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/sanitizer",
3
3
  "type": "module",
4
- "version": "0.2.198",
4
+ "version": "0.2.199",
5
5
  "description": "A fast, native Bun-powered HTML sanitizer with DOMPurify-like features. Protection against XSS and malicious content.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",