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

Don't use dangerous HTML in Linkify #10

Merged
merged 7 commits into from
Dec 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Contributor Author

@erikshestopal erikshestopal Dec 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to add some rules around domain extensions so that URLs like duckduckgo.com in the playground below will also be converted into links?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, probably yes. We can add rules to our doctors for now :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kylealwyn cool. LGTM 🚢


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>"`;