Skip to content
This repository has been archived by the owner on Oct 6, 2020. It is now read-only.

Commit

Permalink
fix(Linkify): remove usage of dangerouslySetInnerHTML (#10)
Browse files Browse the repository at this point in the history
> ### Summary
Fixed an issue with the `Linkify` component allowing execution
of arbitrary HTML/JS that could be used for XSS attacks.
  • Loading branch information
erikshestopal authored and kylealwyn committed Dec 7, 2018
1 parent fca4234 commit 0f2da65
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 16 deletions.
56 changes: 43 additions & 13 deletions src/Linkify/Linkify.js
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);
14 changes: 11 additions & 3 deletions src/Linkify/Linkify.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ Automatically parse links contained in a body of text.

## Example
<Playground>
<Linkify>
Checkout http://google.com. Another one that is not that great is http://bing.com.
</Linkify>
{() => {
const testEvaluatedUrl = 'https://www.facebook.com/heydoctor.co';
return (
<Linkify linkStyle={{ color: 'magenta' }}>
<div>
Checkout http://google.com. <span onmouseover="alert('yo')"
>Another</span> one that's not so great is http://bing.com. You could also checkout duckduckgo.com if you're into that whole privacy thing. <img src="fake.jpg" onError={() => {}} alt="hacker" /> Oh, and you should also check out https://heydoctor.co. <Linkify>I wonder if nested Linkify works {testEvaluatedUrl}</Linkify>
</div>
</Linkify>
);
}}
</Playground>
27 changes: 27 additions & 0 deletions src/Linkify/Linkify.spec.js
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();
});
});
43 changes: 43 additions & 0 deletions src/Linkify/__snapshots__/Linkify.spec.js.snap
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>"`;

0 comments on commit 0f2da65

Please sign in to comment.