vadivam-angular 0.0.42 → 0.0.44

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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ Tree-shakeable 24px outline SVG icon directives for Angular.
8
8
  [![downloads](https://img.shields.io/npm/dw/vadivam-angular?style=flat-square&color=666)](https://www.npmjs.com/package/vadivam-angular)
9
9
  [![license](https://img.shields.io/npm/l/vadivam-angular?style=flat-square&color=111)](https://github.com/praveenjuge/vadivam/blob/main/LICENSE)
10
10
 
11
- ![All Vadivam icons](https://raw.githubusercontent.com/praveenjuge/vadivam/main/apps/docs/public/preview.png?v=0.0.42)
11
+ ![All Vadivam icons](https://raw.githubusercontent.com/praveenjuge/vadivam/main/apps/docs/public/preview.png?v=0.0.44)
12
12
 
13
13
  ```sh
14
14
  npm install vadivam-angular
@@ -1,6 +1,155 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, input, inject, ElementRef, Renderer2, effect, Directive } from '@angular/core';
3
3
 
4
+ const SAFE_ICON_TAGS = new Set(["circle", "ellipse", "line", "path", "polygon", "polyline", "rect"]);
5
+ const GEOMETRY_ATTRIBUTES = {
6
+ circle: new Set(["cx", "cy", "r"]),
7
+ ellipse: new Set(["cx", "cy", "rx", "ry"]),
8
+ line: new Set(["x1", "y1", "x2", "y2"]),
9
+ path: new Set(["d", "pathLength"]),
10
+ polygon: new Set(["points", "pathLength"]),
11
+ polyline: new Set(["points", "pathLength"]),
12
+ rect: new Set(["x", "y", "width", "height", "rx", "ry", "pathLength"]),
13
+ };
14
+ const PRESENTATION_ATTRIBUTES = new Set([
15
+ "class", "className", "id", "role", "key", "opacity", "transform",
16
+ "clip-rule", "clipRule", "fill", "fill-opacity", "fillOpacity", "fill-rule", "fillRule",
17
+ "shape-rendering", "shapeRendering", "stroke", "stroke-dasharray", "strokeDasharray",
18
+ "stroke-dashoffset", "strokeDashoffset", "stroke-linecap", "strokeLinecap",
19
+ "stroke-linejoin", "strokeLinejoin", "stroke-miterlimit", "strokeMiterlimit",
20
+ "stroke-opacity", "strokeOpacity", "stroke-width", "strokeWidth", "vector-effect", "vectorEffect",
21
+ ]);
22
+ const SAFE_ATTRIBUTE_NAME = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
23
+ const SAFE_DATA_ATTRIBUTE = /^data-[A-Za-z0-9_.-]+$/;
24
+ const SAFE_ARIA_ATTRIBUTE = /^aria-[A-Za-z0-9_.-]+$/;
25
+ const SAFE_STYLE_NAME = /^(?:--[A-Za-z0-9_-]+|[A-Za-z_][A-Za-z0-9_-]*)$/;
26
+ const SAFE_EVENT_NAME = /^on(?::[A-Za-z][A-Za-z0-9_.-]*|[A-Za-z][A-Za-z0-9_.-]*)$/;
27
+ const UNSAFE_ROOT_ATTRIBUTE = /^(?:dangerouslySetInnerHTML|innerHTML|innerText|textContent|children|href|xlinkHref|src|srcdoc)$/i;
28
+ const URL_VALUE_ATTRIBUTE = /^(?:fill|stroke|filter|clipPath|clip-path|mask|marker|markerStart|marker-start|markerMid|marker-mid|markerEnd|marker-end|cursor)$/;
29
+ const SCRIPT_SCHEME = /(?:java|vb)script\s*:/i;
30
+ const URL_FUNCTION = /url\s*\(/i;
31
+ const COMPLETE_URL_FUNCTION = /url\s*\(\s*(["']?)([^"')\s]+)\1\s*\)/gi;
32
+ const EXTERNAL_SCHEME = /(?:https?|data|blob|file|ftp)\s*:/i;
33
+ const CSS_ESCAPE = /\\/;
34
+ const CSS_RESOURCE_FUNCTION = /(?:image-set|cross-fade|image|src)\s*\(/i;
35
+ const CSS_IMPORT = /@import\b/i;
36
+ const MAX_ICON_DEPTH = 32;
37
+ const MAX_ICON_NODES = 1024;
38
+ function isRecord(value) {
39
+ return value !== null && typeof value === "object" && !Array.isArray(value);
40
+ }
41
+ function isSafeValue(name, value) {
42
+ if (value === undefined || value === null || typeof value === "function")
43
+ return false;
44
+ if (typeof value === "object")
45
+ return false;
46
+ if (typeof value === "string" && SCRIPT_SCHEME.test(value))
47
+ return false;
48
+ if ((name === "fill" || name === "stroke") && typeof value === "string" && hasExternalUrl(value))
49
+ return false;
50
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
51
+ }
52
+ function hasExternalUrl(value) {
53
+ if (!URL_FUNCTION.test(value))
54
+ return false;
55
+ COMPLETE_URL_FUNCTION.lastIndex = 0;
56
+ let matched = false;
57
+ let match;
58
+ while ((match = COMPLETE_URL_FUNCTION.exec(value)) !== null) {
59
+ matched = true;
60
+ if (!match[2].startsWith("#"))
61
+ return true;
62
+ }
63
+ return !matched;
64
+ }
65
+ function hasUnsafeStyleValue(value) {
66
+ return SCRIPT_SCHEME.test(value) || EXTERNAL_SCHEME.test(value) || CSS_ESCAPE.test(value) ||
67
+ CSS_RESOURCE_FUNCTION.test(value) || CSS_IMPORT.test(value) || hasExternalUrl(value);
68
+ }
69
+ function isSafeIconAttribute(tag, name) {
70
+ return SAFE_ATTRIBUTE_NAME.test(name) && (GEOMETRY_ATTRIBUTES[tag]?.has(name) ||
71
+ PRESENTATION_ATTRIBUTES.has(name) ||
72
+ SAFE_DATA_ATTRIBUTE.test(name) ||
73
+ SAFE_ARIA_ATTRIBUTE.test(name));
74
+ }
75
+ function sanitizeStyle(value) {
76
+ if (typeof value === "string")
77
+ return hasUnsafeStyleValue(value) ? undefined : value;
78
+ if (Array.isArray(value)) {
79
+ const safeValues = value.map(sanitizeStyle).filter((entry) => entry !== undefined);
80
+ return safeValues.length ? safeValues : undefined;
81
+ }
82
+ if (!isRecord(value))
83
+ return undefined;
84
+ const safeStyle = {};
85
+ for (const [name, entry] of Object.entries(value)) {
86
+ if (!SAFE_STYLE_NAME.test(name) || (typeof entry !== "string" && typeof entry !== "number"))
87
+ continue;
88
+ if (typeof entry === "string" && hasUnsafeStyleValue(entry))
89
+ continue;
90
+ safeStyle[name] = entry;
91
+ }
92
+ return Object.keys(safeStyle).length ? safeStyle : undefined;
93
+ }
94
+ function safeRootValue(name, value) {
95
+ if (name === "style")
96
+ return sanitizeStyle(value);
97
+ if (value === undefined || value === null || typeof value === "object" || typeof value === "function")
98
+ return undefined;
99
+ if (typeof value === "string" && URL_VALUE_ATTRIBUTE.test(name) && (SCRIPT_SCHEME.test(value) || hasExternalUrl(value)))
100
+ return undefined;
101
+ return value;
102
+ }
103
+ function sanitizePaint(value, fallback = "currentColor") {
104
+ const safeValue = safeRootValue("stroke", value);
105
+ return typeof safeValue === "string" || typeof safeValue === "number" ? safeValue : fallback;
106
+ }
107
+ function sanitizeNodes(iconNode, activeArrays, state, depth) {
108
+ if (!Array.isArray(iconNode) || depth > MAX_ICON_DEPTH || activeArrays.has(iconNode))
109
+ return [];
110
+ activeArrays.add(iconNode);
111
+ const safeNodes = [];
112
+ for (const node of iconNode) {
113
+ state.count += 1;
114
+ if (state.count > MAX_ICON_NODES)
115
+ break;
116
+ if (!Array.isArray(node) || typeof node[0] !== "string" || !SAFE_ICON_TAGS.has(node[0]) || !isRecord(node[1]))
117
+ continue;
118
+ const [tag, attrs, children] = node;
119
+ const safeAttrs = {};
120
+ for (const [name, value] of Object.entries(attrs)) {
121
+ if (isSafeIconAttribute(tag, name) && isSafeValue(name, value))
122
+ safeAttrs[name] = value;
123
+ }
124
+ safeNodes.push([tag, safeAttrs]);
125
+ }
126
+ activeArrays.delete(iconNode);
127
+ return safeNodes;
128
+ }
129
+ function sanitizeIconNode(iconNode) {
130
+ return sanitizeNodes(iconNode, new WeakSet(), { count: 0 }, 0);
131
+ }
132
+ function sanitizeRootAttributes(attrs, allowEventFunctions = false) {
133
+ if (!isRecord(attrs))
134
+ return {};
135
+ const safeAttrs = {};
136
+ for (const name of Object.keys(attrs)) {
137
+ const value = attrs[name];
138
+ if (/^on/i.test(name)) {
139
+ if (allowEventFunctions && typeof value === "function" && SAFE_EVENT_NAME.test(name)) {
140
+ Object.defineProperty(safeAttrs, name, { enumerable: true, get: () => typeof attrs[name] === "function" ? attrs[name] : undefined });
141
+ }
142
+ continue;
143
+ }
144
+ if (!SAFE_ATTRIBUTE_NAME.test(name) || UNSAFE_ROOT_ATTRIBUTE.test(name) || /^prop/i.test(name))
145
+ continue;
146
+ if (safeRootValue(name, value) === undefined)
147
+ continue;
148
+ Object.defineProperty(safeAttrs, name, { enumerable: true, get: () => safeRootValue(name, attrs[name]) });
149
+ }
150
+ return safeAttrs;
151
+ }
152
+
4
153
  const VADIVAM_CONFIG = new InjectionToken("vadivam-icons", { factory: () => ({}) });
5
154
  function provideVadivamConfig(config) { return { provide: VADIVAM_CONFIG, useValue: Object.freeze({ ...config }) }; }
6
155
  function createVadivamIcon(name, iconNode) { return Object.freeze({ name, iconNode }); }
@@ -43,7 +192,7 @@ class VadivamIconBase {
43
192
  this.generatedClasses.clear();
44
193
  this.decorative = false;
45
194
  const name = this.runtimeName ?? this.name() ?? this.defaultName;
46
- const iconNode = this.runtimeIconNode ?? this.iconNode() ?? this.defaultIconNode;
195
+ const iconNode = sanitizeIconNode(this.runtimeIconNode ?? this.iconNode() ?? this.defaultIconNode);
47
196
  const size = this.size() ?? this.config.size ?? defaults.size;
48
197
  const stroke = this.strokeWidth() ?? this.config.strokeWidth ?? defaults.strokeWidth;
49
198
  const numericSize = Number(size);
@@ -51,7 +200,7 @@ class VadivamIconBase {
51
200
  const absoluteInput = this.absoluteStrokeWidth();
52
201
  const absolute = absoluteInput === "" || absoluteInput === true || absoluteInput === "true" || (absoluteInput == null && this.config.absoluteStrokeWidth === true);
53
202
  const calculated = absolute && Number.isFinite(numericSize) && numericSize !== 0 && Number.isFinite(numericStroke) ? numericStroke * 24 / numericSize : stroke;
54
- const attrs = { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: this.color() ?? this.config.color ?? defaults.color, "stroke-width": calculated, "stroke-linecap": "round", "stroke-linejoin": "round" };
203
+ const attrs = { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: sanitizePaint(this.color() ?? this.config.color), "stroke-width": calculated, "stroke-linecap": "round", "stroke-linejoin": "round" };
55
204
  for (const [key, value] of Object.entries(attrs))
56
205
  this.renderer.setAttribute(this.element, key, String(value));
57
206
  const classes = ["vadivam", name && `vadivam-${name}`, ...(this.config.class ?? "").split(/\s+/)].filter(Boolean);