dalila 1.10.3 → 1.10.4

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.
@@ -492,6 +492,9 @@ function escapeInlineScriptContent(script) {
492
492
  }
493
493
  });
494
494
  }
495
+ function stringifyInlineScriptLiteral(value) {
496
+ return escapeInlineScriptContent(JSON.stringify(value));
497
+ }
495
498
  /**
496
499
  * Generate a minimal inline script to prevent FOUC.
497
500
  *
@@ -499,13 +502,14 @@ function escapeInlineScriptContent(script) {
499
502
  */
500
503
  export function createPreloadScript(options) {
501
504
  const { storageKey, defaultValue, target = 'documentElement', attribute = 'data-theme', storageType = 'localStorage', } = options;
502
- // Use JSON.stringify to safely embed strings (avoid breaking quotes / injection)
503
- const k = JSON.stringify(storageKey);
504
- const d = JSON.stringify(defaultValue);
505
- const a = JSON.stringify(attribute);
505
+ const safeTarget = target === 'body' ? 'body' : 'documentElement';
506
+ const safeStorageType = storageType === 'sessionStorage' ? 'sessionStorage' : 'localStorage';
507
+ const k = stringifyInlineScriptLiteral(storageKey);
508
+ const d = stringifyInlineScriptLiteral(defaultValue);
509
+ const a = stringifyInlineScriptLiteral(attribute);
506
510
  // Still minified
507
- const script = `(function(){try{var s=${storageType}.getItem(${k});var v=s==null?${d}:JSON.parse(s);document.${target}.setAttribute(${a},v)}catch(e){document.${target}.setAttribute(${a},${d})}})();`;
508
- return escapeInlineScriptContent(script);
511
+ const script = `(function(){try{var s=${safeStorageType}.getItem(${k});var v=s==null?${d}:JSON.parse(s);document.${safeTarget}.setAttribute(${a},v)}catch(e){document.${safeTarget}.setAttribute(${a},${d})}})();`;
512
+ return script;
509
513
  }
510
514
  export function createThemeScript(storageKey, defaultTheme = 'light') {
511
515
  return createPreloadScript({
@@ -1,6 +1,5 @@
1
1
  const TRUSTED_POLICY_CACHE_KEY = Symbol.for('dalila.runtime.trustedTypesPolicies');
2
2
  const TRUSTED_POLICY_PARSE_SUFFIX = '--dalila-parse';
3
- const EXECUTABLE_HTML_EVENT_ATTR_PATTERN = /<[^>]+\son[a-z0-9:_-]+\s*=/i;
4
3
  const EXECUTABLE_HTML_URL_ATTR_NAMES = new Set([
5
4
  'href',
6
5
  'src',
@@ -9,7 +8,6 @@ const EXECUTABLE_HTML_URL_ATTR_NAMES = new Set([
9
8
  'action',
10
9
  'poster',
11
10
  ]);
12
- const EXECUTABLE_DATA_URL_PATTERN = /^data:(?:text\/html|application\/xhtml\+xml|image\/svg\+xml)\b/i;
13
11
  function getTrustedPolicyCache() {
14
12
  const host = globalThis;
15
13
  if (host[TRUSTED_POLICY_CACHE_KEY] instanceof Map) {
@@ -42,7 +40,7 @@ function isHtmlAttributeNameChar(code) {
42
40
  && code !== 0x60;
43
41
  }
44
42
  function isTagBoundaryChar(char) {
45
- return !char || /[\s/>]/.test(char);
43
+ return !char || char === '/' || char === '>' || isHtmlWhitespaceCode(char.charCodeAt(0));
46
44
  }
47
45
  function getPreviousNonWhitespaceChar(value, end, start = 0) {
48
46
  for (let index = end - 1; index >= start; index -= 1) {
@@ -82,7 +80,83 @@ function hasExecutableHtmlScriptTag(value) {
82
80
  function hasExecutableProtocol(value) {
83
81
  return value.startsWith('javascript:')
84
82
  || value.startsWith('vbscript:')
85
- || EXECUTABLE_DATA_URL_PATTERN.test(value);
83
+ || value.startsWith('data:');
84
+ }
85
+ function hasExecutableHtmlEventAttribute(value) {
86
+ let index = 0;
87
+ while (index < value.length) {
88
+ const tagStart = value.indexOf('<', index);
89
+ if (tagStart === -1)
90
+ return false;
91
+ let cursor = tagStart + 1;
92
+ const firstCode = value.charCodeAt(cursor);
93
+ if (Number.isNaN(firstCode)
94
+ || value[cursor] === '/'
95
+ || value[cursor] === '!'
96
+ || value[cursor] === '?') {
97
+ index = cursor;
98
+ continue;
99
+ }
100
+ while (cursor < value.length
101
+ && !isHtmlWhitespaceCode(value.charCodeAt(cursor))
102
+ && value[cursor] !== '>') {
103
+ cursor += 1;
104
+ }
105
+ while (cursor < value.length && value[cursor] !== '>') {
106
+ while (cursor < value.length && isHtmlWhitespaceCode(value.charCodeAt(cursor))) {
107
+ cursor += 1;
108
+ }
109
+ if (cursor >= value.length || value[cursor] === '>') {
110
+ break;
111
+ }
112
+ if (value[cursor] === '/') {
113
+ cursor += 1;
114
+ continue;
115
+ }
116
+ const nameStart = cursor;
117
+ while (cursor < value.length && isHtmlAttributeNameChar(value.charCodeAt(cursor))) {
118
+ cursor += 1;
119
+ }
120
+ if (cursor === nameStart) {
121
+ cursor += 1;
122
+ continue;
123
+ }
124
+ const attrName = value.slice(nameStart, cursor).toLowerCase();
125
+ while (cursor < value.length && isHtmlWhitespaceCode(value.charCodeAt(cursor))) {
126
+ cursor += 1;
127
+ }
128
+ if (value[cursor] !== '=') {
129
+ continue;
130
+ }
131
+ if (attrName.startsWith('on')) {
132
+ return true;
133
+ }
134
+ cursor += 1;
135
+ while (cursor < value.length && isHtmlWhitespaceCode(value.charCodeAt(cursor))) {
136
+ cursor += 1;
137
+ }
138
+ if (cursor >= value.length) {
139
+ break;
140
+ }
141
+ const quote = value[cursor];
142
+ if (quote === '"' || quote === '\'') {
143
+ cursor += 1;
144
+ const closingQuoteIndex = value.indexOf(quote, cursor);
145
+ if (closingQuoteIndex === -1) {
146
+ break;
147
+ }
148
+ cursor = closingQuoteIndex + 1;
149
+ continue;
150
+ }
151
+ while (cursor < value.length
152
+ && !isHtmlWhitespaceCode(value.charCodeAt(cursor))
153
+ && value[cursor] !== '>') {
154
+ cursor += 1;
155
+ }
156
+ }
157
+ index = cursor + 1;
158
+ }
159
+ return false;
86
160
  }
87
161
  function hasExecutableHtmlUrlAttribute(value) {
88
162
  let index = 0;
@@ -194,7 +268,7 @@ export function hasExecutableHtmlSinkPattern(value) {
194
268
  if (!value)
195
269
  return false;
196
270
  return hasExecutableHtmlScriptTag(value)
197
- || EXECUTABLE_HTML_EVENT_ATTR_PATTERN.test(value)
271
+ || hasExecutableHtmlEventAttribute(value)
198
272
  || hasExecutableHtmlUrlAttribute(value);
199
273
  }
200
274
  function getTrustedTypesApi() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dalila",
3
- "version": "1.10.3",
3
+ "version": "1.10.4",
4
4
  "description": "DOM-first reactive framework based on signals",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -116,19 +116,27 @@ function createForbiddenPathError() {
116
116
  return error;
117
117
  }
118
118
 
119
- function isPathInsideRoot(candidatePath) {
119
+ function toServedRelativePath(candidatePath) {
120
+ if (typeof candidatePath !== 'string' || candidatePath.length === 0) {
121
+ return null;
122
+ }
123
+
120
124
  const normalizedPath = path.resolve(candidatePath);
121
125
  const relativePath = path.relative(rootDirAbs, normalizedPath);
122
- return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
123
- }
126
+ if (relativePath === '') {
127
+ return '';
128
+ }
124
129
 
125
- function normalizeServedPath(candidatePath) {
126
- if (typeof candidatePath !== 'string' || candidatePath.length === 0) {
130
+ if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
127
131
  return null;
128
132
  }
129
133
 
130
- const normalizedPath = path.resolve(candidatePath);
131
- return isPathInsideRoot(normalizedPath) ? normalizedPath : null;
134
+ return relativePath;
135
+ }
136
+
137
+ function normalizeServedPath(candidatePath) {
138
+ const relativePath = toServedRelativePath(candidatePath);
139
+ return relativePath == null ? null : path.join(rootDirAbs, relativePath);
132
140
  }
133
141
 
134
142
  function statServedPath(targetPath) {
@@ -219,6 +227,10 @@ function stringifyInlineScriptPayload(value, indent = 0) {
219
227
  .join('\n');
220
228
  }
221
229
 
230
+ function stringifyInlineScriptLiteral(value) {
231
+ return escapeInlineScriptContent(JSON.stringify(value));
232
+ }
233
+
222
234
  function normalizePreloadStorageType(storageType) {
223
235
  return storageType === 'sessionStorage' ? 'sessionStorage' : 'localStorage';
224
236
  }
@@ -763,14 +775,14 @@ function findTypeScriptFiles(dir, files = []) {
763
775
  */
764
776
  function generatePreloadScript(name, defaultValue, storageType = 'localStorage') {
765
777
  const safeStorageType = normalizePreloadStorageType(storageType);
766
- const payload = JSON.stringify({
778
+ const payload = stringifyInlineScriptLiteral({
767
779
  key: name,
768
780
  defaultValue,
769
781
  storageType: safeStorageType,
770
782
  });
771
- const fallbackValue = JSON.stringify(defaultValue);
783
+ const fallbackValue = stringifyInlineScriptLiteral(defaultValue);
772
784
  const script = `(function(){try{var p=${payload};var s=window[p.storageType];var v=s.getItem(p.key);document.documentElement.setAttribute('data-theme',v==null?p.defaultValue:JSON.parse(v))}catch(e){document.documentElement.setAttribute('data-theme',${fallbackValue})}})();`;
773
- return escapeInlineScriptContent(script);
785
+ return script;
774
786
  }
775
787
 
776
788
  function renderPreloadScriptTags(baseDir) {