@bygghammar/cms-platform 0.92.1 → 0.92.2
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/CHANGELOG.md +13 -0
- package/package.json +1 -1
- package/src/html/sanitize-html.ts +43 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,19 @@ matches `package.json` exactly. See
|
|
|
8
8
|
Versions 0.1.0 through 0.78.0 predate this repository and were reconstructed
|
|
9
9
|
from the published npm tarballs.
|
|
10
10
|
|
|
11
|
+
## 0.92.2
|
|
12
|
+
|
|
13
|
+
- Fix rich-text attribute escaping. `sanitizeHtml` read attribute values as
|
|
14
|
+
raw text and escaped them again, so every save and every render added an
|
|
15
|
+
`amp;` to each `&` in a link: `?calendardates=…&trp=…` reached the browser
|
|
16
|
+
with a query key named `amp;trp`. Attribute values are now decoded once,
|
|
17
|
+
checked, and escaped exactly once, so sanitizing is idempotent through save
|
|
18
|
+
and render. The URL allowlist, style checks and target check judge the
|
|
19
|
+
decoded value, the one the browser will use. Text content is unchanged.
|
|
20
|
+
- Links stored before this release keep their extra levels until an editor
|
|
21
|
+
saves the correct URL; they no longer grow on each save.
|
|
22
|
+
- Two scheduling test suites no longer depend on the date they run on.
|
|
23
|
+
|
|
11
24
|
## 0.92.1
|
|
12
25
|
|
|
13
26
|
- Fix the page-builder save and publish RPC call by removing the unsupported
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bygghammar/cms-platform",
|
|
3
|
-
"version": "0.92.
|
|
3
|
+
"version": "0.92.2",
|
|
4
4
|
"description": "Shared CMS platform: blocks, block types, i18n core, UI primitives and design-token contract used by every Bygghammar/Voyara site.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -85,6 +85,48 @@ function escapeAttr(v: string): string {
|
|
|
85
85
|
return v.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/** The named references the editor and browsers write into attribute values. */
|
|
89
|
+
const NAMED_REFERENCES: Record<string, string> = {
|
|
90
|
+
amp: "&",
|
|
91
|
+
AMP: "&",
|
|
92
|
+
lt: "<",
|
|
93
|
+
LT: "<",
|
|
94
|
+
gt: ">",
|
|
95
|
+
GT: ">",
|
|
96
|
+
quot: '"',
|
|
97
|
+
QUOT: '"',
|
|
98
|
+
apos: "'",
|
|
99
|
+
nbsp: "\u00a0",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* An attribute value as a browser reads it: its character references decoded.
|
|
104
|
+
*
|
|
105
|
+
* Stored markup is HTML, so `?a=1&b=2` means `?a=1&b=2`. Everything below
|
|
106
|
+
* judges the decoded value, and `escapeAttr` encodes it exactly once on the
|
|
107
|
+
* way out. Reading the raw text instead let `escapeAttr` encode the editor's
|
|
108
|
+
* own `&` a second time, so every save and every render added a level and
|
|
109
|
+
* a link's `&b=` arrived in the browser as a key named `amp;b`.
|
|
110
|
+
*
|
|
111
|
+
* Numeric references, and the named ones above, are decoded. Any other name
|
|
112
|
+
* is left as written, so it is escaped as text and reaches the browser as that
|
|
113
|
+
* literal text. Either way the browser ends up with exactly the string that
|
|
114
|
+
* was checked: `javascript:` is judged as `javascript:`, and an
|
|
115
|
+
* undecoded `javascript:` never becomes one.
|
|
116
|
+
*/
|
|
117
|
+
function decodeAttr(value: string): string {
|
|
118
|
+
return value.replace(
|
|
119
|
+
/&(?:#([0-9]{1,7})|#[xX]([0-9a-fA-F]{1,6})|([a-zA-Z][a-zA-Z0-9]{0,31}));/g,
|
|
120
|
+
(reference, decimal?: string, hex?: string, name?: string) => {
|
|
121
|
+
if (name !== undefined) return NAMED_REFERENCES[name] ?? reference;
|
|
122
|
+
const code = decimal !== undefined ? Number.parseInt(decimal, 10) : Number.parseInt(hex ?? "", 16);
|
|
123
|
+
// What a browser does with these: the replacement character.
|
|
124
|
+
if (code === 0 || code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) return "\ufffd";
|
|
125
|
+
return String.fromCodePoint(code);
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
88
130
|
const ATTR_RE = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/g;
|
|
89
131
|
|
|
90
132
|
function sanitizeAttrs(tag: string, raw: string): string {
|
|
@@ -94,7 +136,7 @@ function sanitizeAttrs(tag: string, raw: string): string {
|
|
|
94
136
|
ATTR_RE.lastIndex = 0;
|
|
95
137
|
while ((m = ATTR_RE.exec(raw))) {
|
|
96
138
|
const name = m[1].toLowerCase();
|
|
97
|
-
let value = m[3] ?? m[4] ?? m[5] ?? "";
|
|
139
|
+
let value = decodeAttr(m[3] ?? m[4] ?? m[5] ?? "");
|
|
98
140
|
if (name.startsWith("on")) continue;
|
|
99
141
|
if (!GLOBAL_ATTRS.has(name) && !allowed?.has(name)) continue;
|
|
100
142
|
if (name === "href" || name === "src" || name === "srcset") {
|