|
1 | 1 | /**
|
2 |
| - * Copyright 2019 Google LLC |
| 2 | + * Copyright 2020 Google LLC |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * 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<{
|
44 | 44 | return (
|
45 | 45 | <script
|
46 | 46 | type="application/ld+json"
|
47 |
| - dangerouslySetInnerHTML={{ __html: JSON.stringify(this.props.item) }} |
| 47 | + dangerouslySetInnerHTML={{ |
| 48 | + __html: JSON.stringify(this.props.item, safeJsonLdReplacer) |
| 49 | + }} |
48 | 50 | />
|
49 | 51 | );
|
50 | 52 | }
|
51 | 53 | }
|
| 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 | + "&": "&", |
| 71 | + "<": "<", |
| 72 | + ">": ">", |
| 73 | + '"': """, |
| 74 | + "'": "'" |
| 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