This repository has been archived by the owner on Oct 6, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(Linkify): remove usage of dangerouslySetInnerHTML (#10)
> ### Summary Fixed an issue with the `Linkify` component allowing execution of arbitrary HTML/JS that could be used for XSS attacks.
- Loading branch information
1 parent
fca4234
commit 0f2da65
Showing
4 changed files
with
124 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,49 @@ | ||
import React from 'react'; | ||
import styled from 'styled-components'; | ||
|
||
const Container = styled.span` | ||
a { | ||
color: inherit; | ||
text-decoration: underline; | ||
} | ||
`; | ||
const URL_REGEX = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi; | ||
|
||
const REGEX = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi; | ||
const Link = ({ href, linkStyle }) => ( | ||
<a | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
style={{ | ||
color: 'inherit', | ||
textDecoration: 'underline', | ||
...linkStyle, | ||
}} | ||
href={href}> | ||
{href} | ||
</a> | ||
); | ||
|
||
const parse = string => string.replace(REGEX, url => `<a target="_blank" href="${url}">${url}</a>`); | ||
const linkify = ({ children, linkStyle = {} }) => | ||
React.Children.map(children, child => { | ||
if (!child) return null; | ||
|
||
const Linkify = props => ( | ||
<Container style={props.style || {}} dangerouslySetInnerHTML={{ __html: parse(props.children) }} /> | ||
); | ||
if (typeof child === 'string') { | ||
const matches = child.match(URL_REGEX); | ||
|
||
if (!matches) { | ||
return child; | ||
} | ||
|
||
// Clone the string for manipulation | ||
let string = `${child}`; | ||
|
||
// Run through the url matches to create our array of string parts and Link components | ||
const stringParts = matches.reduce((parts, url) => { | ||
const urlStartIndex = string.indexOf(url); | ||
const partBeforeUrl = string.substring(0, urlStartIndex); | ||
parts.push(partBeforeUrl, <Link href={url} linkStyle={linkStyle} />); | ||
string = string.substring(urlStartIndex + url.length); | ||
return parts; | ||
}, []); | ||
|
||
// Add any leftovers after the last url | ||
return [...stringParts, string]; | ||
} | ||
|
||
return linkify({ children: child.props.children, linkStyle }); | ||
}); | ||
|
||
export default Linkify; | ||
export default props => linkify(props); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import React from 'react'; | ||
import { renderWithTheme } from '../../test/utils'; | ||
import Linkify from './Linkify'; | ||
|
||
describe('Linkify', () => { | ||
test('converts links to anchor tags', () => { | ||
const component = renderWithTheme(<Linkify>Hello! https://google.com is a cool site.</Linkify>); | ||
|
||
expect(component).toMatchSnapshot(); | ||
}); | ||
|
||
test('escapes HTML entities', () => { | ||
const component = renderWithTheme( | ||
<Linkify>{`<img src="fake.jpg" onError={() => {}} alt="hacker" /><span>heheh got hacked</span>`}</Linkify> | ||
); | ||
|
||
expect(component).toMatchSnapshot(); | ||
}); | ||
|
||
test('can receive linkStyle', () => { | ||
const component = renderWithTheme( | ||
<Linkify linkStyle={{ color: 'magenta' }}>Hello! https://google.com is a cool site.</Linkify> | ||
); | ||
|
||
expect(component).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Linkify can receive linkStyle 1`] = ` | ||
Array [ | ||
"Hello! ", | ||
<a | ||
href="https://google.com" | ||
rel="noopener noreferrer" | ||
style={ | ||
Object { | ||
"color": "magenta", | ||
"textDecoration": "underline", | ||
} | ||
} | ||
target="_blank" | ||
> | ||
https://google.com | ||
</a>, | ||
" is a cool site.", | ||
] | ||
`; | ||
|
||
exports[`Linkify converts links to anchor tags 1`] = ` | ||
Array [ | ||
"Hello! ", | ||
<a | ||
href="https://google.com" | ||
rel="noopener noreferrer" | ||
style={ | ||
Object { | ||
"color": "inherit", | ||
"textDecoration": "underline", | ||
} | ||
} | ||
target="_blank" | ||
> | ||
https://google.com | ||
</a>, | ||
" is a cool site.", | ||
] | ||
`; | ||
|
||
exports[`Linkify escapes HTML entities 1`] = `"<img src=\\"fake.jpg\\" onError={() => {}} alt=\\"hacker\\" /><span>heheh got hacked</span>"`; |