u2ui 1.5.14 → 1.5.16

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/el/ico/ico.js CHANGED
@@ -131,13 +131,11 @@ async function loadSvgString(url) {
131
131
  }
132
132
  }
133
133
 
134
- const svg = await loadCached(url);
135
- // temporary cache svg
136
-
137
134
  // external element
138
- if (hash) { // todo, cache the svgDoc?
139
- const parser = new DOMParser();
140
- const svgDoc = parser.parseFromString(svg, "image/svg+xml");
135
+ if (hash) {
136
+ url.hash = '';
137
+ const svgDoc = await (cachedDocuments[url] ??= loadCached(url).then(svg =>
138
+ new DOMParser().parseFromString(svg, "image/svg+xml")));
141
139
  const element = svgDoc.getElementById(hash);
142
140
  if (element) {
143
141
  return element.outerHTML;
@@ -147,10 +145,11 @@ async function loadSvgString(url) {
147
145
  }
148
146
 
149
147
  // external document
150
- return svg;
148
+ return loadCached(url);
151
149
  }
152
150
 
153
151
  const cachedRequests = {};
152
+ const cachedDocuments = {};
154
153
 
155
154
  async function loadCached(url) {
156
155
  return cachedRequests[url] ??= fetch(url, {cache: "force-cache"}).then(res => {
@@ -38,8 +38,13 @@ Event attributes, comments, and data attributes
38
38
  are not enabled by default. Inline `style` is: it executes nothing, and where
39
39
  foreign presentation is unwanted the Unstyle policy already removes it from what
40
40
  an import brought — a policy that must not carry it at all narrows `attributes`.
41
- Relative URLs use the explicit `relative` protocol token. Known URL attributes
42
- without a matching protocol rule are removed rather than accepted implicitly.
41
+ A URL that resolves inside the document — `/page`, `page.html`, `#anchor`,
42
+ `?q=1` — passes wherever the attribute has a rule at all. It reaches nowhere the
43
+ document is not, so there is nothing for a policy to allow and nothing an
44
+ application can forbid by forgetting to name it. `//host/x` and `\\host/x` name no
45
+ scheme either but resolve elsewhere: they are absolute URLs in disguise, judged
46
+ as the `http`/`https` ones they turn into. Known URL attributes without a
47
+ matching protocol rule are removed rather than accepted implicitly.
43
48
 
44
49
  `policy.with(options)` copies a policy with single axes replaced, and
45
50
  `policyFor(config, base)` is what a host gets: its own `--u2-rte-elements`,
@@ -52,7 +57,7 @@ returns the base itself.
52
57
  const policy = new SanitizePolicy({
53
58
  elements: ['p', 'a', 'strong'],
54
59
  attributes: {'*': ['class'], a: ['href', 'rel']},
55
- protocols: {a: {href: ['https', 'relative']}},
60
+ protocols: {a: {href: ['https']}},
56
61
  });
57
62
  ```
58
63
 
@@ -127,6 +132,20 @@ through `--u2-rte-classes`; external HTML then cannot smuggle in classes the
127
132
  host does not know, and the same list drives the style control and presentation
128
133
  cleanup.
129
134
 
135
+ ## Possible extensions
136
+
137
+ Neither is planned; both are written down because the protocol list invites them
138
+ and answers neither.
139
+
140
+ - **A host allowlist.** A protocol list says `https` is fine and cannot say
141
+ *whose* `https`. An application that wants its own images and no foreign
142
+ tracking pixel has no way to state it here, and stating it as a protocol —
143
+ which an earlier `relative` token tried — describes the wrong axis.
144
+ - **Reading the document's CSP.** `img-src`, `media-src` and `frame-src` already
145
+ declare what the page will load at all. An editor that read them would stop
146
+ offering what the browser is about to refuse, without a second list to keep in
147
+ step.
148
+
130
149
  ## TODO
131
150
 
132
151
  - Add contextual parsing for table and list insertion sites.
@@ -20,14 +20,14 @@ const DEFAULT_ATTRIBUTES = Object.freeze({
20
20
  time: Object.freeze(['datetime']),
21
21
  });
22
22
  const DEFAULT_PROTOCOLS = Object.freeze({
23
- a: Object.freeze({href: Object.freeze(['http', 'https', 'mailto', 'relative', 'tel'])}),
24
- blockquote: Object.freeze({cite: Object.freeze(['http', 'https', 'relative'])}),
23
+ a: Object.freeze({href: Object.freeze(['http', 'https', 'mailto', 'tel'])}),
24
+ blockquote: Object.freeze({cite: Object.freeze(['http', 'https'])}),
25
25
  // `data:` belongs to an image and to nothing else here: an image executes
26
26
  // nothing, not even an SVG one, which browsers draw in a script-free
27
27
  // context. In a navigating attribute the same protocol is a page, so `a`
28
28
  // and the citations keep it out.
29
- img: Object.freeze({src: Object.freeze(['data', 'http', 'https', 'relative'])}),
30
- q: Object.freeze({cite: Object.freeze(['http', 'https', 'relative'])}),
29
+ img: Object.freeze({src: Object.freeze(['data', 'http', 'https'])}),
30
+ q: Object.freeze({cite: Object.freeze(['http', 'https'])}),
31
31
  });
32
32
 
33
33
  // Elements a browser never renders the children of. Unwrapping one turns source — a stylesheet, a
@@ -84,8 +84,7 @@ export class SanitizePolicy {
84
84
  const protocols = this.#protocols[element.localName.toLowerCase()]?.[attributeName]
85
85
  || this.#protocols['*']?.[attributeName];
86
86
  if (!protocols) return false;
87
- const protocol = urlProtocol(value, base);
88
- return protocol !== null && protocols.includes(protocol);
87
+ return allowedUrl(value, base, protocols);
89
88
  }
90
89
 
91
90
  // Reduces a subtree to the elements this policy allows, keeping the content
@@ -217,14 +216,17 @@ function protocolGroups(value) {
217
216
  return Object.freeze(result);
218
217
  }
219
218
 
220
- function urlProtocol(value, base) {
221
- const input = String(value).trim();
222
- const compact = input.replace(/[\u0000-\u0020]/g, '');
223
- if (!/^[a-z][a-z\d+.-]*:/i.test(compact)) return 'relative';
219
+ // A url naming no scheme that lands on the document's own origin — `/page`, `#anchor`, `?q=1` —
220
+ // reaches nowhere the document is not: nothing to allow, and nothing to forbid by forgetting to
221
+ // name it. `//host` and `\\host` name none either but go elsewhere, so their real scheme decides.
222
+ function allowedUrl(value, base, protocols) {
223
+ const input = String(value).trim().replace(/[\u0000-\u0020]/g, '');
224
+ const own = !/^([a-z][a-z\d+.-]*:|[/\\]{2})/i.test(input);
224
225
  try {
225
- return new URL(input, base).protocol.slice(0, -1).toLowerCase();
226
+ const url = new URL(input, base);
227
+ return own ? url.origin === new URL(base).origin : protocols.includes(url.protocol.slice(0, -1));
226
228
  } catch {
227
- return null;
229
+ return own; // nothing to resolve against — an opaque document keeps its own urls
228
230
  }
229
231
  }
230
232
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u2ui",
3
- "version": "1.5.14",
3
+ "version": "1.5.16",
4
4
  "description": "HTML Advanced: More Capabilities, Same Feel, Same Simplicity",
5
5
  "keywords": ["web-components","pattern-library","component-library","ui-toolkit","design-system"],
6
6
  "author": "Tobias Buschor",