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 2 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
53 changes: 40 additions & 13 deletions src/Linkify/Linkify.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
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 detectLinks = ({ children, linkStyle = {} }) =>
React.Children.map(children, child => {
if (!child) return null;

const parse = string => string.replace(REGEX, url => `<a target="_blank" href="${url}">${url}</a>`);
if (typeof child === 'string') {
const words = child.split(' ');
return words.map((word, index) => {
const isLastWord = words.length === index - 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

copy pasta from my example but this should be words.length - 1 === index


const Linkify = props => (
<Container style={props.style || {}} dangerouslySetInnerHTML={{ __html: parse(props.children) }} />
);
if (URL_REGEX.test(word)) {
return (
<>
<a
key={word}
target="_blank"
rel="noopener noreferrer"
style={{
color: 'inherit',
textDecoration: 'underline',
...linkStyle,
}}
href={word}>
{word}
</a>

export default Linkify;
{isLastWord ? '' : ' '}
</>
);
}

return isLastWord ? word : `${word} `;
});
}

if (React.isValidElement(child)) {
return detectLinks({ children: child.props.children, linkStyle });
}

return child;
});

export default props => detectLinks(props);
22 changes: 22 additions & 0 deletions src/Linkify/Linkify.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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" />

Choose a reason for hiding this comment

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

shouldn't this all be in a template string to test escaping?

<span>heheh got hacked</span>
</Linkify>
);

expect(component).toMatchSnapshot();
});
});
33 changes: 33 additions & 0 deletions src/Linkify/__snapshots__/Linkify.spec.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

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`] = `
Array [
"heheh ",
"got ",
"hacked ",
]
`;