Skip to content

Commit 22e7dec

Browse files
committed
Implement a Safe JSON-LD replacer
Per https://www.w3.org/TR/json-ld11/#restrictions-for-contents-of-json-ld-script-elements See the parent issue in w3c/json-ld-syntax#100 where this was created. Fixes #9
1 parent 617d8fd commit 22e7dec

File tree

1 file changed

+52
-2
lines changed

1 file changed

+52
-2
lines changed

src/json-ld.tsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2019 Google LLC
2+
* Copyright 2020 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -44,8 +44,58 @@ export class JsonLd<T extends Thing> extends React.Component<{
4444
return (
4545
<script
4646
type="application/ld+json"
47-
dangerouslySetInnerHTML={{ __html: JSON.stringify(this.props.item) }}
47+
dangerouslySetInnerHTML={{
48+
__html: JSON.stringify(this.props.item, safeJsonLdReplacer)
49+
}}
4850
/>
4951
);
5052
}
5153
}
54+
55+
type JsonValueScalar = string | boolean | number;
56+
type JsonValue =
57+
| JsonValueScalar
58+
| Array<JsonValue>
59+
| { [key: string]: JsonValue };
60+
type JsonReplacer = (_: string, value: JsonValue) => JsonValue | undefined;
61+
62+
/**
63+
* A replacer for JSON.stringify to strip JSON-LD of illegal HTML entities
64+
* per https://www.w3.org/TR/json-ld11/#restrictions-for-contents-of-json-ld-script-elements
65+
*/
66+
const safeJsonLdReplacer: JsonReplacer = (() => {
67+
// Replace per https://www.w3.org/TR/json-ld11/#restrictions-for-contents-of-json-ld-script-elements
68+
// Solution from https://stackoverflow.com/a/5499821/864313
69+
const entities = Object.freeze({
70+
"&": "&amp;",
71+
"<": "&lt;",
72+
">": "&gt;",
73+
'"': "&quot;",
74+
"'": "&apos;"
75+
});
76+
const replace = (t: string): string =>
77+
entities[t as keyof typeof entities] || t;
78+
79+
return (_: string, value: JsonValue): JsonValue | undefined => {
80+
switch (typeof value) {
81+
case "object":
82+
return value; // JSON.stringify will recursively call replacer.
83+
case "number":
84+
case "boolean":
85+
case "bigint":
86+
return value; // These values are not risky.
87+
case "string":
88+
return value.replace(/[&<>'"]/g, replace);
89+
default: {
90+
// We shouldn't expect other types.
91+
isNever(value);
92+
93+
// JSON.stringify will remove this element.
94+
return undefined;
95+
}
96+
}
97+
};
98+
})();
99+
100+
// Utility: Assert never
101+
function isNever(_: never): void {}

0 commit comments

Comments
 (0)