@bygghammar/cms-platform 0.92.0 → 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 +23 -0
- package/package.json +1 -1
- package/src/html/sanitize-html.ts +43 -1
- package/src/server/pages-core.ts +13 -9
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,29 @@ 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
|
+
|
|
24
|
+
## 0.92.1
|
|
25
|
+
|
|
26
|
+
- Fix the page-builder save and publish RPC call by removing the unsupported
|
|
27
|
+
`_source_page_id` argument. The database supplies the source page internally,
|
|
28
|
+
preserving self-reveal protection. No migration is added or changed, and
|
|
29
|
+
platform contract v3 is unchanged.
|
|
30
|
+
- Clarify publication RPC errors so an argument mismatch is not presented as
|
|
31
|
+
proof that migrations must be replayed. Include only a recognized diagnostic
|
|
32
|
+
code and request a schema comparison before applying changes.
|
|
33
|
+
|
|
11
34
|
## 0.92.0
|
|
12
35
|
|
|
13
36
|
**Breaking: platform contract v3.** A site pinned to this package must call
|
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") {
|
package/src/server/pages-core.ts
CHANGED
|
@@ -377,9 +377,9 @@ export async function getPageForEditCore({ context }: PagesDeps, key: string) {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
/**
|
|
380
|
-
* Codes and wording that mean "this installation has
|
|
381
|
-
*
|
|
382
|
-
* from PostgreSQL itself.
|
|
380
|
+
* Codes and wording that mean "this installation has no such function, or none
|
|
381
|
+
* that takes these arguments". `PGRST202`/`PGRST203` come from PostgREST, which
|
|
382
|
+
* answers the same way for both; `42883`/`42P01` from PostgreSQL itself.
|
|
383
383
|
*/
|
|
384
384
|
const FUNCTION_MISSING = new Set(["PGRST202", "PGRST203", "42883", "42P01"]);
|
|
385
385
|
|
|
@@ -423,10 +423,14 @@ async function publicationRpc(
|
|
|
423
423
|
) {
|
|
424
424
|
// A slice of the generated migration manifest rather than a second copy of
|
|
425
425
|
// the names: an operator sent after a file this package has stopped
|
|
426
|
-
// shipping is worse off than one told nothing.
|
|
426
|
+
// shipping is worse off than one told nothing. Only a code from the set
|
|
427
|
+
// above is repeated; the backend's own message is not.
|
|
427
428
|
throw new Error(
|
|
428
|
-
`${whenFailed}:
|
|
429
|
-
`${
|
|
429
|
+
`${whenFailed}: the database has no ${fn} that accepts this call` +
|
|
430
|
+
`${FUNCTION_MISSING.has(code) ? ` (${code})` : ""}. Either the migrations ` +
|
|
431
|
+
`${installedBy.join(" and ")} shipped with this package are not installed, ` +
|
|
432
|
+
`or the installed ${fn} takes different arguments than this package sends. ` +
|
|
433
|
+
`Compare the database's schema with this package before applying anything.`,
|
|
430
434
|
);
|
|
431
435
|
}
|
|
432
436
|
throw new Error(message || whenFailed);
|
|
@@ -474,10 +478,10 @@ export async function savePageVersionCore(
|
|
|
474
478
|
_note: input.note ?? null,
|
|
475
479
|
_publish: Boolean(input.publish),
|
|
476
480
|
_actor_email: actorEmail(context),
|
|
481
|
+
// No `_source_page_id`: the function takes none, and names `_page_id` as
|
|
482
|
+
// the source of every reveal itself, so a countdown pointing at the page
|
|
483
|
+
// being published is still refused rather than hiding it.
|
|
477
484
|
_reveals: reveals,
|
|
478
|
-
// Named so a countdown pointing at the page being published is refused
|
|
479
|
-
// rather than hiding it until its own deadline.
|
|
480
|
-
_source_page_id: input.pageId,
|
|
481
485
|
_audit: input.publish
|
|
482
486
|
? {
|
|
483
487
|
description:
|