Skip to content

Commit 1c3aa76

Browse files
authored
Merge pull request #10 from google/ssr-xss-fix
Use a Safe JSON-LD replacer
2 parents da84d27 + 22e7dec commit 1c3aa76

3 files changed

Lines changed: 56 additions & 6 deletions

File tree

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"@types/react": "^16.7.22",
1919
"react": "^15.4.1",
2020
"schema-dts": ">=0.4.0",
21-
"typescript": ">=3.1.6"
21+
"typescript": "^3.8.3"
2222
},
2323
"dependencies": {},
2424
"peerDependencies": {

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)