Skip to content

Commit

Permalink
Feature(core): escape special text in SSR(and not escape with dangero…
Browse files Browse the repository at this point in the history
…usSetInnerHTML) (#44)

* Feature: escape html with createTextNode

* Chore: add test for escaping html

* Feature: dangerouslySetInnerHTML without escaping html
  • Loading branch information
Shinyaigeek committed Nov 17, 2021
1 parent 9505af3 commit 2b9ba64
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 8 deletions.
11 changes: 6 additions & 5 deletions src/core.ts
Expand Up @@ -101,8 +101,6 @@ export const render = (component: any, parent: HTMLElement | null = null, remove
})
else appendChildren(parent, _render(el))
}
//@ts-ignore
if (parent.ssr) return parent.ssr
return parent
}
// returning one child or an array of children
Expand Down Expand Up @@ -248,14 +246,17 @@ export const h = (tagNameOrComponent: any, props: any, ...children: any) => {
// handle events
else if (isEvent(element, p.toLowerCase()))
element.addEventListener(p.toLowerCase().substring(2), (e: any) => props[p](e))
else if (p === 'dangerouslySetInnerHTML') {
const fragment = document.createElement("fragment")
fragment.innerHTML = props[p].__html
element.appendChild(fragment)
}
else if (/className/i.test(p)) console.warn('You can use "class" instead of "className".')
else if (typeof props[p] !== 'undefined') element.setAttribute(p, props[p])
}

appendChildren(element, children)

if (ref) ref(element)
// @ts-ignore
if (element.ssr) return element.ssr
return element
return element as any
}
13 changes: 12 additions & 1 deletion src/ssr.ts
Expand Up @@ -37,6 +37,7 @@ export class HTMLElementSSR {
ssr: string
tagName: string
isSelfClosing: boolean = false
nodeType: null | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 = null

constructor(tag: string) {
this.tagName = tag
Expand All @@ -57,6 +58,8 @@ export class HTMLElementSSR {
'track',
'wbr'
]

this.nodeType = 1

if (selfClosing.indexOf(tag) >= 0) {
this.ssr = `<${tag} />`
Expand All @@ -75,6 +78,10 @@ export class HTMLElementSSR {
return reg.exec(this.ssr)?.[2] ?? ''
}

set innerHTML(text) {
this.ssr = text
}

get innerText() {
const reg = /(^<[^>]+>)(.+)?(<\/[a-z]+>$|\/>$)/gm
return reg.exec(this.ssr)?.[2] ?? ''
Expand All @@ -89,6 +96,10 @@ export class HTMLElementSSR {
return { length: 1 }
}

toString() {
return this.ssr
}

setAttributeNS(name: string, value: string) {
this.setAttribute(name, value)
}
Expand Down Expand Up @@ -150,7 +161,7 @@ export class DocumentSSR {
}

createTextNode(text: string) {
return text
return escapeHtml(text)
}

querySelector(_query: string) {
Expand Down
29 changes: 27 additions & 2 deletions test/ssr.test.tsx
Expand Up @@ -89,7 +89,32 @@ test('should render without errors', async () => {
})

test("should escape attribute's string value", () => {
const content = Nano.h('div', { id: '"hoge' }, '<span>span</span>')
const content = Nano.h('div', { id: '"hoge' }, 'hoge')
const html = renderSSR(content)
expect(html).toBe('<div id="&quot;hoge"><span>span</span></div>')
expect(html).toBe('<div id="&quot;hoge">hoge</div>')
})

test('should escape text node', () => {
const content = Nano.h('div', {}, '<span>span</span>')
const html = renderSSR(content)
expect(html).toBe('<div>&lt;span&gt;span&lt;/span&gt;</div>')
})

test('should render dangerouslySetInnerHtml without escaping', () => {
const code = `<div>should not escape</div>`
const App = () => {
return (
<div>
<div>hoge</div>
<Img src="some-url" placeholder="placeholder-url" />
<div dangerouslySetInnerHTML={{ __html: code }} />
</div>
)
}

const app = Nano.renderSSR(<App />)
const { body } = Helmet.SSR(app)

expect(body).toBe(`<div><div>hoge</div><img src="placeholder-url" /><div><div>should not escape</div></div></div>`)
expect(spy).not.toHaveBeenCalled()
})

0 comments on commit 2b9ba64

Please sign in to comment.