Skip to content

Commit db47a9e

Browse files
Merge pull request #69 from contentstack/development
Development
2 parents 421e293 + f4a66c2 commit db47a9e

File tree

8 files changed

+308
-7
lines changed

8 files changed

+308
-7
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023-2024 Contentstack
3+
Copyright (c) 2024-2025 Contentstack
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

+32
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ On the other hand, the `customTextWrapper` parser function provides the followin
161161
- `child`: The HTML string that specifies the child element
162162
- `value`: The value passed against the child element
163163

164+
164165
You can use the following customized JSON RTE Serializer code to convert your JSON RTE field data into HTML format.
165166

166167
```javascript
@@ -356,12 +357,43 @@ The resulting JSON-formatted data will look as follows:
356357

357358
## Automatic Conversion
358359

360+
> **_Note_**: `src` url's provided for social-embeds and embed items will by default be <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI">uri encoded</a>.
361+
359362
By default, the JSON Rich Text Editor field supports limited HTML tags within the editor. Due to this, the JSON RTE Serializer tool is not able to recognize each and every standard HTML tag.
360363

361364
To help the JSON RTE Serializer recognize and process additional tags that are commonly used across HTML, you can use the automatic conversion option. When using this option, you need to pass the `allowNonStandardTags: true` parameter within the `jsonToHtml` or `htmlToJson` method to manipulate the working of the JSON RTE Serializer package as per your requirements. When you pass this parameter, it customizes your JSON RTE Serializer code to allow the support for all standard HTML-recognized tags or element types in the JSON Rich Text Editor field.
362365

363366
### Convert JSON to HTML
364367

368+
#### HTML Attribute Name and Value Sanitization
369+
370+
371+
This project ensures that HTML attributes are properly validated and sanitized according to the W3C HTML specification. It validates attribute names based on the HTML standards and sanitizes attribute values to ensure correct rendering and security, particularly against cross-site scripting (XSS) vulnerabilities.
372+
373+
#### Attribute Name Guidelines
374+
375+
All HTML attribute names must conform to the [W3C HTML specification](https://www.w3.org/TR/2012/WD-html-markup-20120329/syntax.html#attribute-name). These guidelines specify the following rules:
376+
377+
- **Printable ASCII Characters:** Attribute names must consist only of printable ASCII characters.
378+
- **Case-Insensitive:** Attribute names are case-insensitive, but lowercase is preferred for consistency.
379+
- **No Special Characters:** Attribute names cannot contain spaces or special characters such as `=`, `>`, `<`, `"`, etc.
380+
- **Allowed Attributes:** Attributes such as `xmlns`, `aria-*`, `data-*`, and others defined by HTML5 standards are allowed and must follow specific rules.
381+
382+
##### Important Note:
383+
If an attribute name does not conform to these rules, the attribute will be **dropped** from the element.
384+
385+
#### Attribute Value Guidelines
386+
387+
The values of HTML attributes are sanitized to ensure proper rendering and to mitigate security risks, such as Cross-Site Scripting (XSS). This sanitization process involves replacing HTML entities (like `&lt;`, `&gt;`, `&amp;`, etc.) with their corresponding characters and removing any invalid or unsafe characters.
388+
389+
Here are some common HTML entities and their replacements:
390+
391+
- `&lt;``<`
392+
- `&gt;``>`
393+
- `&amp;``&`
394+
395+
396+
<hr>
365397
You can pass the `allowNonStandardTags: true` parameter within the `jsonToHtml` method to allow the JSON RTE Serializer tool to recognize standard HTML tags or element types and convert them into JSON format.
366398

367399
You can use the following customized JSON RTE Serializer code to convert your JSON RTE field data into HTML format.

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/json-rte-serializer",
3-
"version": "2.0.12",
3+
"version": "2.1.0",
44
"description": "This Package converts Html Document to Json and vice-versa.",
55
"main": "lib/index.js",
66
"module": "lib/index.mjs",

src/toRedactor.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import kebbab from 'lodash.kebabcase'
22
import isEmpty from 'lodash.isempty'
3-
43
import {IJsonToHtmlElementTags, IJsonToHtmlOptions, IJsonToHtmlTextTags} from './types'
54
import isPlainObject from 'lodash.isplainobject'
5+
import {replaceHtmlEntities, forbiddenAttrChars } from './utils'
66

77
const ELEMENT_TYPES: IJsonToHtmlElementTags = {
88
'blockquote': (attrs: string, child: string) => {
@@ -379,6 +379,9 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string
379379
}
380380
if (attrsJson['width']) {
381381
let width = attrsJson['width']
382+
if(typeof width === 'number'){
383+
width = width.toString()
384+
}
382385
if (width.slice(width.length - 1) === '%') {
383386
style = `width: ${allattrs['width'] + '%'}; height: ${attrsJson['height'] ? attrsJson['height'] : 'auto'};`
384387
} else {
@@ -494,12 +497,20 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string
494497
}
495498
figureStyles.fieldsEdited.push(figureStyles.caption)
496499
}
500+
501+
if (jsonValue['type'] === 'social-embeds' || jsonValue['type'] === 'embed') {
502+
attrsJson['src'] = encodeURI(allattrs['src']);
503+
}
504+
497505
if(!(options?.customElementTypes && !isEmpty(options.customElementTypes) && options.customElementTypes[jsonValue['type']])) {
498506
delete attrsJson['url']
499507
}
500508
delete attrsJson['redactor-attributes']
501509
Object.entries(attrsJson).forEach((key) => {
502-
return key[1] ? (key[1] !== '' ? (attrs += `${key[0]}="${key[1]}" `) : '') : ''
510+
if (forbiddenAttrChars.some(char => key[0].includes(char))) {
511+
return;
512+
}
513+
return key[1] ? (key[1] !== '' ? (attrs += `${key[0]}="${replaceHtmlEntities(key[1])}" `) : '') : ''
503514
})
504515
attrs = (attrs.trim() ? ' ' : '') + attrs.trim()
505516
}
@@ -556,7 +567,7 @@ export const toRedactor = (jsonValue: any,options?:IJsonToHtmlOptions) : string
556567
if(['td','th'].includes(jsonValue['type'])){
557568
if(jsonValue?.['attrs']?.['void']) return ''
558569
}
559-
570+
560571
attrs = (attrs.trim() ? ' ' : '') + attrs.trim()
561572

562573
return ELEMENT_TYPES[orgType || jsonValue['type']](attrs, children,jsonValue, figureStyles)

src/utils/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function replaceHtmlEntities(str: string): string {
2+
return String(str)
3+
.replace(/&/g, '&amp;')
4+
.replace(/</g, '&lt;')
5+
.replace(/>/g, '&gt;')
6+
.replace(/"/g, '&quot;');
7+
}
8+
9+
export const forbiddenAttrChars = ['"', "'", '>','<', '/', '='];

test/expectedJson.ts

+205
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,211 @@ export default {
19991999
}
20002000
],
20012001
"htmlUpdated": "<p></p><img asset_uid=\"blt5523ee02703e39f5\" src=\"https://images.com/captain_pardip.jpg\" width=\"24.193548387096776\" height=\"auto\" style=\"width: 24.193548387096776%; height: auto;height: auto;\" type=\"asset\" sys-style-type=\"download\"/><p></p><iframe src=\"https://www.***REMOVED***.com/embed/CSvFpBOe8eY\"></iframe><img asset_uid=\"blta2aad0332073026c\" src=\"https://images.com/logo_1.jpg\" height=\"auto\" type=\"asset\" sys-style-type=\"download\"/>"
2002+
},
2003+
"RT-360":{
2004+
"html": [
2005+
`<iframe src=\"https://www.youtube.com/watch?v=Gw7EqoOYC9A%22%3E%3C/iframe%3E%3Cscript%3Ealert(document.cookie)%3C/script%3E%3Ciframe%20\" width=\"560\" height=\"320\" data-type=\"social-embeds\" ></iframe><iframe allowfullscreen=\"true\" src=\"https://www.youtube.com/watch?v=Gw7EqoOYC9A%22%3E%3C/iframe%3E%3Cscript%3Ealert(document.cookie)%3C/script%3E%3Ciframe%20\"></iframe>`,
2006+
`<iframe width="560" height="320" data-type="social-embeds" ></iframe><iframe allowfullscreen=\"true\"></iframe>`,
2007+
'<iframe src=\"www.youtube.com/watch?v=Gw7EqoOYC9A\" width=\"560\" height=\"320\" data-type=\"social-embeds\" ></iframe><iframe allowfullscreen=\"true\" src=\"www.youtube.com/embed/VD6xJq8NguY\"></iframe>',
2008+
`<iframe src=\"https://www.youtube.com/embed/Gw7EqoOYC9A?si=bWdnezma6qFAePQU\" width=\"560\" height=\"320\" data-type=\"social-embeds\" ></iframe><iframe allowfullscreen=\"true\" src=\"https://www.youtube.com/embed/Gw7EqoOYC9A?si=bWdnezma6qFAePQU\"></iframe>`,
2009+
`<iframe src="null" width="560" height="320" title=" This is for &lt;/p&gt;testing &lt;/p&gt; purpose 'only' " data-type="social-embeds" ></iframe>`,
2010+
`<iframe 123="456" src="https://www.youtube.com/embed/Gw7EqoOYC9A?si=bWdnezma6qFAePQU" width="560" height="320" status="Active" data-type="social-embeds" ></iframe>`
2011+
],
2012+
"json":
2013+
[
2014+
{
2015+
"type": "doc",
2016+
"attrs": {},
2017+
"uid": "18396bf67f1f4b0a9da57643ac0542ca",
2018+
"children": [
2019+
{
2020+
"uid": "45a850acbeb949db86afe415625ad1ce",
2021+
"type": "social-embeds",
2022+
"attrs": {
2023+
"src": "https://www.youtube.com/watch?v=Gw7EqoOYC9A\"></iframe><script>alert(document.cookie)</script><iframe ",
2024+
"width": 560,
2025+
"height": 320
2026+
},
2027+
"children": [
2028+
{
2029+
"text": ""
2030+
}
2031+
]
2032+
},
2033+
{
2034+
"uid": "d3c2ab78a5e547b082f95dc01123b0c1",
2035+
"type": "doc",
2036+
"_version": 11,
2037+
"attrs": {},
2038+
"children": [
2039+
{
2040+
"uid": "87fed1cc68ce435caa0f71d17788c618",
2041+
"type": "embed",
2042+
"attrs": {
2043+
"src": "https://www.youtube.com/watch?v=Gw7EqoOYC9A\"></iframe><script>alert(document.cookie)</script><iframe ",
2044+
"redactor-attributes": {
2045+
"allowfullscreen": true
2046+
}
2047+
}
2048+
}
2049+
]
2050+
}
2051+
],
2052+
"_version": 1
2053+
},
2054+
{
2055+
"type": "doc",
2056+
"attrs": {},
2057+
"uid": "18396bf67f1f4b0a9da57643ac0542ca",
2058+
"children": [
2059+
{
2060+
"uid": "45a850acbeb949db86afe415625ad1ce",
2061+
"type": "social-embeds",
2062+
"attrs": {
2063+
"src": "",
2064+
"width": 560,
2065+
"height": 320
2066+
},
2067+
"children": [
2068+
{
2069+
"text": ""
2070+
}
2071+
]
2072+
},
2073+
{
2074+
"uid": "d3c2ab78a5e547b082f95dc01123b0c1",
2075+
"type": "doc",
2076+
"_version": 11,
2077+
"attrs": {},
2078+
"children": [
2079+
{
2080+
"uid": "87fed1cc68ce435caa0f71d17788c618",
2081+
"type": "embed",
2082+
"attrs": {
2083+
"src": "",
2084+
"redactor-attributes": {
2085+
"allowfullscreen": true
2086+
}
2087+
}
2088+
}
2089+
]
2090+
}
2091+
],
2092+
"_version": 1
2093+
},
2094+
{
2095+
"type": "doc",
2096+
"attrs": {},
2097+
"uid": "18396bf67f1f4b0a9da57643ac0542ca",
2098+
"children": [
2099+
{
2100+
"uid": "45a850acbeb949db86afe415625ad1ce",
2101+
"type": "social-embeds",
2102+
"attrs": {
2103+
"src": "www.youtube.com/watch?v=Gw7EqoOYC9A",
2104+
"width": 560,
2105+
"height": 320
2106+
},
2107+
"children": [
2108+
{
2109+
"text": ""
2110+
}
2111+
]
2112+
},
2113+
{
2114+
"uid": "d3c2ab78a5e547b082f95dc01123b0c1",
2115+
"type": "doc",
2116+
"_version": 11,
2117+
"attrs": {},
2118+
"children": [
2119+
{
2120+
"uid": "87fed1cc68ce435caa0f71d17788c618",
2121+
"type": "embed",
2122+
"attrs": {
2123+
"src": "www.youtube.com/embed/VD6xJq8NguY",
2124+
"redactor-attributes": {
2125+
"allowfullscreen": true
2126+
}
2127+
}
2128+
}
2129+
]
2130+
}
2131+
],
2132+
"_version": 1
2133+
},
2134+
{
2135+
"type": "doc",
2136+
"attrs": {},
2137+
"uid": "18396bf67f1f4b0a9da57643ac0542ca",
2138+
"children": [
2139+
{
2140+
"uid": "45a850acbeb949db86afe415625ad1ce",
2141+
"type": "social-embeds",
2142+
"attrs": {
2143+
"src": "https://www.youtube.com/embed/Gw7EqoOYC9A?si=bWdnezma6qFAePQU",
2144+
"width": 560,
2145+
"height": 320
2146+
},
2147+
"children": [
2148+
{
2149+
"text": ""
2150+
}
2151+
]
2152+
},
2153+
{
2154+
"uid": "d3c2ab78a5e547b082f95dc01123b0c1",
2155+
"type": "doc",
2156+
"_version": 11,
2157+
"attrs": {},
2158+
"children": [
2159+
{
2160+
"uid": "87fed1cc68ce435caa0f71d17788c618",
2161+
"type": "embed",
2162+
"attrs": {
2163+
"src": "https://www.youtube.com/embed/Gw7EqoOYC9A?si=bWdnezma6qFAePQU",
2164+
"redactor-attributes": {
2165+
"allowfullscreen": true
2166+
}
2167+
}
2168+
}
2169+
]
2170+
}
2171+
],
2172+
"_version": 1
2173+
},
2174+
{
2175+
uid: "45a850acbeb949db86afe415625ad1ce",
2176+
type: "social-embeds",
2177+
attrs: {
2178+
src: "null",
2179+
width: 560,
2180+
height: 320,
2181+
title: " This is for </p>testing </p> purpose 'only' "
2182+
},
2183+
children: [{ text: "" }],
2184+
},
2185+
{
2186+
"uid": "45a850acbeb949db86afe415625ad1ce",
2187+
"type": "social-embeds",
2188+
"attrs": {
2189+
"123": "456",
2190+
"src": "https://www.youtube.com/embed/Gw7EqoOYC9A?si=bWdnezma6qFAePQU",
2191+
"width": 560,
2192+
"height": 320,
2193+
"<p>ding": 234,
2194+
"status": "Active",
2195+
"emptyKey<": "12",
2196+
"country/": "USA"
2197+
},
2198+
"children": [
2199+
{
2200+
"text": ""
2201+
}
2202+
]
2203+
},
2204+
2205+
]
2206+
20022207
}
20032208

20042209
}

0 commit comments

Comments
 (0)