Skip to content

Commit 00c7791

Browse files
committed
fix: replace HTML_LOWER_CASE regex with Set to prevent false matches on custom elements
The regex could incorrectly match custom element properties (e.g. channelId matching the `ch` prefix, resulting in `channelid`). Using an explicit Set ensures only known HTML attributes are lowercased. Fixes #451
1 parent 4c47e70 commit 00c7791

5 files changed

Lines changed: 86 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- [#446](https://github.com/preactjs/preact-render-to-string/pull/446) [`b7b288c`](https://github.com/preactjs/preact-render-to-string/commit/b7b288c68ed8c97ed8eb6c26929111debdc03435) Thanks [@JoviDeCroock](https://github.com/JoviDeCroock)! - Fix issues regarding streaming full HTML documents
88

9+
- Replace `HTML_LOWER_CASE` regex with an explicit `Set` for camelCase HTML attribute name lookup. The regex could incorrectly match custom element properties (e.g. `channelId` matching the `ch` prefix, resulting in `channelid`). Using a `Set` ensures only known HTML attributes are lowercased.
10+
911
## 6.6.5
1012

1113
### Patch Changes

package-lock.json

Lines changed: 38 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ function _renderToString(
666666
? 'panose-1'
667667
: name.replace(/([A-Z])/g, '-$1').toLowerCase();
668668
}
669-
} else if (HTML_LOWER_CASE.test(name)) {
669+
} else if (HTML_LOWER_CASE.has(name)) {
670670
name = name.toLowerCase();
671671
}
672672
}

src/lib/util.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,50 @@ export const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|link|meta|
44
// oxlint-disable-next-line no-control-regex
55
export const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
66
export const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)([A-Z])/;
7-
export const HTML_LOWER_CASE = /^(?:accessK|auto[A-Z]|cell|ch|col|cont|cross|dateT|encT|form[A-Z]|frame|hrefL|inputM|maxL|minL|noV|playsI|popoverT|readO|rowS|src[A-Z]|tabI|useM|item[A-Z])/;
7+
export const HTML_LOWER_CASE = new Set([
8+
'accessKey',
9+
'autoCapitalize',
10+
'autoComplete',
11+
'autoCorrect',
12+
'autoFocus',
13+
'autoPlay',
14+
'cellPadding',
15+
'cellSpacing',
16+
'charSet',
17+
'colSpan',
18+
'contentEditable',
19+
'contextMenu',
20+
'controlsList',
21+
'crossOrigin',
22+
'dateTime',
23+
'encType',
24+
'formAction',
25+
'formEncType',
26+
'formMethod',
27+
'formNoValidate',
28+
'formTarget',
29+
'frameBorder',
30+
'hrefLang',
31+
'inputMode',
32+
'maxLength',
33+
'minLength',
34+
'noValidate',
35+
'playsInline',
36+
'popoverTarget',
37+
'popoverTargetAction',
38+
'readOnly',
39+
'rowSpan',
40+
'srcDoc',
41+
'srcLang',
42+
'srcSet',
43+
'tabIndex',
44+
'useMap',
45+
'itemScope',
46+
'itemType',
47+
'itemID',
48+
'itemRef',
49+
'itemProp'
50+
]);
851
export const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|color|dominant|enable|fill|flood|font|glyph[^R]|horiz|image|letter|lighting|marker[^WUH]|overline|panose|pointe|paint|rendering|shape|stop|strikethrough|stroke|text[^L]|transform|underline|unicode|units|^v[^i]|^w|^xH/;
952

1053
// Boolean DOM properties that translate to enumerated ('true'/'false') attributes

src/pretty.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ function _renderToStringPretty(
299299
? 'panose-1'
300300
: name.replace(/([A-Z])/g, '-$1').toLowerCase();
301301
}
302-
} else if (HTML_LOWER_CASE.test(name)) {
302+
} else if (HTML_LOWER_CASE.has(name)) {
303303
name = name.toLowerCase();
304304
}
305305

0 commit comments

Comments
 (0)