Skip to content

Commit

Permalink
feat: dangerouslySetInnerHTML property is removed. All the string pro…
Browse files Browse the repository at this point in the history
…ps will get encoded and dangerouslySetInnerHTML will be used
  • Loading branch information
olexiyk committed Aug 27, 2019
1 parent 38e60d3 commit 19da0b8
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 62 deletions.
13 changes: 3 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,11 @@ will add the following to your markup (will be minified):
| jsonldtype | String | The value of the @type description in the json-ld body: `"@type": "Product"` |
| schema | Object (json) | This should be the schema that you want for your structured data node: `{name: "It is awesome", reviewBody: "This is great!"}` |

#### JSONLD node propTypes

| PropType | Value | Description |
| --------------------- | ------- | ----------------------------------------------------------------------------- |
| dangerouslyExposeHtml | Boolean | Set this to render the json within script tag using `dangerouslySetInnerHTML` |

#### Schema node PropTypes

| PropType | Value | Description |
| -------- | ------ | -------------------------------------------------------------------------------------------------- |
| parentID | String | Sets the id of the schema that becomes a reference that the children point to `"@id": "product-x"` |
| id | String | similar to parentID but uses the ID on itself |
| PropType | Value | Description |
| -------- | ------ | --------------------------------------------- |
| id | String | similar to parentID but uses the ID on itself |

### Preset Components

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"release": "spire release"
},
"dependencies": {
"he": "^1.2.0",
"prop-types": "^15.6.0"
},
"devDependencies": {
Expand Down
9 changes: 1 addition & 8 deletions src/JSONLD.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,17 @@ export const JSONLD = props => {
: Object.assign({ "@context": "https://schema.org/" }, firstChild);
}

return props.dangerouslyExposeHtml ? (
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(json) }}
/>
) : (
<script type="application/ld+json">{JSON.stringify(json)}</script>
);
};

JSONLD.propTypes = {
additionalType: PropTypes.object,
dangerouslyExposeHtml: PropTypes.bool,
children: PropTypes.node
};

JSONLD.defaultProps = {
dangerouslyExposeHtml: false
};

export default JSONLD;
12 changes: 10 additions & 2 deletions src/core/GenericNode.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import JSONLDAbstractNode from "../JSONLDAbstractNode";
import { encode } from "he";

class GenericNode extends JSONLDAbstractNode {
getJSON({ type, jsonldtype, ...schema }) {
Expand All @@ -7,12 +8,19 @@ class GenericNode extends JSONLDAbstractNode {
"@type": jsonldtype || this.getSchemaOrgType(),
...schema
};
// delete props that are `null`
Object.keys(details).forEach(key => {
if (details[key] === null) delete details[key];
});
// escape string prop values
const clearProps = Object.assign(
...Object.entries(details).map(([key, value]) => ({
[key]: typeof value === "string" ? encode(value) : value
}))
);
return type
? Object.assign({ [type]: details }, ...parseChildren)
: Object.assign(details, ...parseChildren);
? Object.assign({ [type]: clearProps }, ...parseChildren)
: Object.assign(clearProps, ...parseChildren);
}
}

Expand Down
26 changes: 26 additions & 0 deletions src/core/__tests__/GenericNode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,30 @@ describe("GenerticNode", () => {
)
).toMatchSnapshot();
});
it("escapes json xss", () => {
expect(
renderer.create(
<JSONLD>
<GenericNode
type="review"
jsonldtype="Review"
reviewBody={`</script><script>alert('XSS');</script>`}
/>
</JSONLD>
)
).toMatchSnapshot();
});
it("escapes javascript xss", () => {
expect(
renderer.create(
<JSONLD>
<GenericNode
type="review"
jsonldtype="Review"
reviewBody={`\\";alert('XSS');//`}
/>
</JSONLD>
)
).toMatchSnapshot();
});
});
67 changes: 52 additions & 15 deletions src/core/__tests__/__snapshots__/GenericNode.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,41 +1,78 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`GenerticNode escapes javascript xss 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Review\\",\\"reviewBody\\":\\"\\\\\\\\&#x22;;alert(&#x27;XSS&#x27;);//\\"}",
}
}
type="application/ld+json"
/>
`;

exports[`GenerticNode escapes json xss 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Review\\",\\"reviewBody\\":\\"&#x3C;/script&#x3E;&#x3C;script&#x3E;alert(&#x27;XSS&#x27;);&#x3C;/script&#x3E;\\"}",
}
}
type="application/ld+json"
/>
`;

exports[`GenerticNode filters out null and false child nodes 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Review\\",\\"name\\":\\"It is awesome\\",\\"reviewBody\\":\\"This is great!\\",\\"itemReviewed\\":{\\"@type\\":\\"Product\\",\\"@id\\":\\"product-x\\"},\\"locationCreated\\":{\\"@type\\":\\"AdministrativeArea\\",\\"name\\":\\"Chicago, IL\\"}}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"Review","name":"It is awesome","reviewBody":"This is great!","itemReviewed":{"@type":"Product","@id":"product-x"},"locationCreated":{"@type":"AdministrativeArea","name":"Chicago, IL"}}
</script>
/>
`;

exports[`GenerticNode filters out one child which is false 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Review\\",\\"name\\":\\"It is awesome\\"}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"Review","name":"It is awesome"}
</script>
/>
`;

exports[`GenerticNode filters out one child which is null 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Review\\",\\"name\\":\\"It is awesome\\"}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"Review","name":"It is awesome"}
</script>
/>
`;

exports[`GenerticNode filters out properties that have null values 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Review\\",\\"name\\":\\"It is awesome\\"}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"Review","name":"It is awesome"}
</script>
/>
`;

exports[`GenerticNode renders question to QAPage mainEntity as Question type 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Review\\",\\"name\\":\\"It is awesome\\",\\"reviewBody\\":\\"This is great!\\",\\"itemReviewed\\":{\\"@type\\":\\"Product\\",\\"@id\\":\\"product-x\\"},\\"author\\":{\\"@type\\":\\"Person\\",\\"name\\":\\"Cool Carl\\"},\\"locationCreated\\":{\\"@type\\":\\"AdministrativeArea\\",\\"name\\":\\"Chicago, IL\\"}}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"Review","name":"It is awesome","reviewBody":"This is great!","itemReviewed":{"@type":"Product","@id":"product-x"},"author":{"@type":"Person","name":"Cool Carl"},"locationCreated":{"@type":"AdministrativeArea","name":"Chicago, IL"}}
</script>
/>
`;
36 changes: 24 additions & 12 deletions src/core/__tests__/__snapshots__/GenericNodeCollection.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,44 @@

exports[`GenerticNodeCollection do not render collection of empty array in it 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Review\\",\\"name\\":\\"With collection of empty array in it\\",\\"author\\":null}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"Review","name":"With collection of empty array in it","author":null}
</script>
/>
`;

exports[`GenerticNodeCollection do not render empty collection 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Review\\",\\"name\\":\\"With empty collection\\",\\"author\\":null}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"Review","name":"With empty collection","author":null}
</script>
/>
`;

exports[`GenerticNodeCollection filters out null and false nodes from a collection 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Cool Carl\\"},{\\"@type\\":\\"Person\\",\\"name\\":\\"Brave Mark\\"}]}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","author":[{"@type":"Person","name":"Cool Carl"},{"@type":"Person","name":"Brave Mark"}]}
</script>
/>
`;

exports[`GenerticNodeCollection filters renders GenericNode with null as type 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Cool Carl\\"}]}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","author":[{"@type":"Person","name":"Cool Carl"}]}
</script>
/>
`;
9 changes: 6 additions & 3 deletions src/schemas/__tests__/__snapshots__/Article.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

exports[`Article example renders article with image 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Article\\",\\"mainEntityOfPage\\":\\"institution/ResearchGate/post/Free_eBook-Evolution_and_Application_of_3D_Cell_Culture_and_Analysis\\",\\"image\\":\\"https://example.org/article_image.jpeg\\",\\"headline\\":\\"Free eBook - Evolution and Application of 3D Cell Culture and Analysis\\",\\"datePublished\\":\\"\\",\\"dateModified\\":\\"\\",\\"publisher\\":{\\"@type\\":\\"Organization\\",\\"url\\":\\"https://example.org/institution/ResearchGate\\",\\"name\\":\\"ResearchGate\\",\\"location\\":{\\"@type\\":\\"Place\\",\\"name\\":\\"Berlin, Germany\\"},\\"logo\\":{\\"@type\\":\\"ImageObject\\",\\"url\\":\\"https://example.org/ii/institution.image/image_url.jpeg\\"}},\\"author\\":{\\"@type\\":\\"Organization\\",\\"name\\":\\"ResearchGate\\"}}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"Article","mainEntityOfPage":"institution/ResearchGate/post/Free_eBook-Evolution_and_Application_of_3D_Cell_Culture_and_Analysis","image":"https://example.org/article_image.jpeg","headline":"Free eBook - Evolution and Application of 3D Cell Culture and Analysis","datePublished":"","dateModified":"","publisher":{"@type":"Organization","url":"https://example.org/institution/ResearchGate","name":"ResearchGate","location":{"@type":"Place","name":"Berlin, Germany"},"logo":{"@type":"ImageObject","url":"https://example.org/ii/institution.image/image_url.jpeg"}},"author":{"@type":"Organization","name":"ResearchGate"}}
</script>
/>
`;
18 changes: 12 additions & 6 deletions src/schemas/__tests__/__snapshots__/Organisation.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@

exports[`Organization example renders Organization with department 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Organization\\",\\"url\\":\\"https://example.org/institution/ResearchGate\\",\\"name\\":\\"ResearchGate\\",\\"location\\":{\\"@type\\":\\"Place\\",\\"name\\":\\"Berlin, Germany\\"},\\"logo\\":{\\"@type\\":\\"ImageObject\\",\\"url\\":\\"https://example.org/ii/institution.image/image_url.jpeg\\"},\\"department\\":{\\"@type\\":\\"Organization\\",\\"url\\":\\"https://example.org/institution/ResearchGate/department/Engineering\\",\\"name\\":\\"Engineering\\"}}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"Organization","url":"https://example.org/institution/ResearchGate","name":"ResearchGate","location":{"@type":"Place","name":"Berlin, Germany"},"logo":{"@type":"ImageObject","url":"https://example.org/ii/institution.image/image_url.jpeg"},"department":{"@type":"Organization","url":"https://example.org/institution/ResearchGate/department/Engineering","name":"Engineering"}}
</script>
/>
`;

exports[`Organization example renders Organization with logo image 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"Organization\\",\\"url\\":\\"https://example.org/institution/ResearchGate\\",\\"name\\":\\"ResearchGate\\",\\"location\\":{\\"@type\\":\\"Place\\",\\"name\\":\\"Berlin, Germany\\"},\\"logo\\":{\\"@type\\":\\"ImageObject\\",\\"url\\":\\"https://example.org/ii/institution.image/image_url.jpeg\\"}}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"Organization","url":"https://example.org/institution/ResearchGate","name":"ResearchGate","location":{"@type":"Place","name":"Berlin, Germany"},"logo":{"@type":"ImageObject","url":"https://example.org/ii/institution.image/image_url.jpeg"}}
</script>
/>
`;
9 changes: 6 additions & 3 deletions src/schemas/__tests__/__snapshots__/QAPage.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

exports[`QAPage renders question to QAPage mainEntity as Question 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@type\\":\\"QAPage\\",\\"url\\":\\"http://example.com/qa-page\\",\\"mainEntity\\":{\\"@type\\":\\"Question\\",\\"name\\":\\"title\\",\\"answerCount\\":5,\\"text\\":\\"question text\\",\\"dateCreated\\":\\"2019-05-01\\",\\"author\\":{\\"@type\\":\\"Person\\",\\"mainEntityOfPage\\":\\"http://example.com/author2\\",\\"name\\":\\"Author of a question\\"},\\"acceptedAnswer\\":{\\"@type\\":\\"Answer\\",\\"dateCreated\\":\\"2019-08-15\\",\\"text\\":\\"test accepted answer\\",\\"upvoteCount\\":20,\\"url\\":\\"http://example.com/answer1\\",\\"author\\":{\\"@type\\":\\"Person\\",\\"mainEntityOfPage\\":\\"http://example.com/author1\\",\\"name\\":\\"Author of an accepted answer\\",\\"memberOf\\":{\\"@type\\":\\"Organization\\",\\"name\\":\\"answer, without collection\\"}}},\\"suggestedAnswer\\":[{\\"@type\\":\\"Answer\\",\\"text\\":\\"some text\\",\\"url\\":\\"http://example.com/answer2\\",\\"dateCreated\\":\\"2019-04-11\\",\\"upvoteCount\\":0,\\"author\\":{\\"@type\\":\\"Person\\",\\"mainEntityOfPage\\":\\"http://example.com/author3\\",\\"name\\":\\"another authot\\"}},{\\"@type\\":\\"Answer\\",\\"text\\":\\"answer with multiple authors\\",\\"url\\":\\"http://example.com/answer3\\",\\"dateCreated\\":\\"2019-04-01\\",\\"upvoteCount\\":1,\\"author\\":[{\\"@type\\":\\"Person\\",\\"mainEntityOfPage\\":\\"http://example.com/author3\\",\\"name\\":\\"Author 1 of a second suggested answer\\"},{\\"@type\\":\\"Person\\",\\"mainEntityOfPage\\":\\"http://example.com/author4\\",\\"name\\":\\"Author 2 of a second suggested answer\\",\\"memberOf\\":{\\"@type\\":\\"Organization\\",\\"name\\":\\"in collection\\"}}]}]}}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@type":"QAPage","url":"http://example.com/qa-page","mainEntity":{"@type":"Question","name":"title","answerCount":5,"text":"question text","dateCreated":"2019-05-01","author":{"@type":"Person","mainEntityOfPage":"http://example.com/author2","name":"Author of a question"},"acceptedAnswer":{"@type":"Answer","dateCreated":"2019-08-15","text":"test accepted answer","upvoteCount":20,"url":"http://example.com/answer1","author":{"@type":"Person","mainEntityOfPage":"http://example.com/author1","name":"Author of an accepted answer","memberOf":{"@type":"Organization","name":"answer, without collection"}}},"suggestedAnswer":[{"@type":"Answer","text":"some text","url":"http://example.com/answer2","dateCreated":"2019-04-11","upvoteCount":0,"author":{"@type":"Person","mainEntityOfPage":"http://example.com/author3","name":"another authot"}},{"@type":"Answer","text":"answer with multiple authors","url":"http://example.com/answer3","dateCreated":"2019-04-01","upvoteCount":1,"author":[{"@type":"Person","mainEntityOfPage":"http://example.com/author3","name":"Author 1 of a second suggested answer"},{"@type":"Person","mainEntityOfPage":"http://example.com/author4","name":"Author 2 of a second suggested answer","memberOf":{"@type":"Organization","name":"in collection"}}]}]}}
</script>
/>
`;
9 changes: 6 additions & 3 deletions src/schemas/__tests__/__snapshots__/Review.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

exports[`Review Page renders reviews in graph 1`] = `
<script
dangerouslySetInnerHTML={
Object {
"__html": "{\\"@context\\":\\"https://schema.org/\\",\\"@graph\\":[{\\"@type\\":\\"Review\\",\\"name\\":\\"It&#x27;s awesome\\",\\"reviewBody\\":\\"This is Great! My family loves it\\",\\"datePublished\\":\\"11/22/1963\\",\\"author\\":{\\"@type\\":\\"Person\\",\\"name\\":\\"Jerry\\"},\\"locationCreated\\":{\\"@type\\":\\"AdministrativeArea\\",\\"name\\":\\"Chicago, IL\\"},\\"reviewRating\\":{\\"@type\\":\\"Rating\\",\\"ratingValue\\":5},\\"itemReviewed\\":{\\"@type\\":\\"Product\\",\\"name\\":\\"Product Name\\",\\"@id\\":\\"product-x\\"}},{\\"@type\\":\\"Review\\",\\"name\\":\\"Very cool\\",\\"reviewBody\\":\\"I like this a lot. Very cool product\\",\\"datePublished\\":\\"11/22/1963\\",\\"author\\":{\\"@type\\":\\"Person\\",\\"name\\":\\"Cool Carl\\"},\\"locationCreated\\":{\\"@type\\":\\"AdministrativeArea\\",\\"name\\":\\"Chicago, IL\\"},\\"reviewRating\\":{\\"@type\\":\\"Rating\\",\\"ratingValue\\":4},\\"itemReviewed\\":{\\"@type\\":\\"Product\\",\\"name\\":\\"Product Name\\",\\"@id\\":\\"product-x\\"}}]}",
}
}
type="application/ld+json"
>
{"@context":"https://schema.org/","@graph":[{"@type":"Review","name":"It's awesome","reviewBody":"This is Great! My family loves it","datePublished":"11/22/1963","author":{"@type":"Person","name":"Jerry"},"locationCreated":{"@type":"AdministrativeArea","name":"Chicago, IL"},"reviewRating":{"@type":"Rating","ratingValue":5},"itemReviewed":{"@type":"Product","name":"Product Name","@id":"product-x"}},{"@type":"Review","name":"Very cool","reviewBody":"I like this a lot. Very cool product","datePublished":"11/22/1963","author":{"@type":"Person","name":"Cool Carl"},"locationCreated":{"@type":"AdministrativeArea","name":"Chicago, IL"},"reviewRating":{"@type":"Rating","ratingValue":4},"itemReviewed":{"@type":"Product","name":"Product Name","@id":"product-x"}}]}
</script>
/>
`;
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5005,6 +5005,11 @@ has@^1.0.1, has@^1.0.3:
dependencies:
function-bind "^1.1.1"

he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==

hook-std@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/hook-std/-/hook-std-2.0.0.tgz#ff9aafdebb6a989a354f729bb6445cf4a3a7077c"
Expand Down

0 comments on commit 19da0b8

Please sign in to comment.