Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"d3": "3",
"deepmerge": "^2.1.1",
"dnd-core": "^2.5.1",
"dompurify": "^1.0.11",
"file-saver": "^1.3.8",
"firebase": "^5.8.3",
"isomorphic-fetch": "^2.2.1",
Expand Down
44 changes: 44 additions & 0 deletions src/browser/components/clickable-urls.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2002-2019 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
* This file is part of Neo4j.
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import React from 'react'
import { sanitize } from 'dompurify'

export default function ClickableUrls ({ text }) {
return (
<span
dangerouslySetInnerHTML={{
__html: sanitize(convertUrlsToHrefTags(text))
}}
/>
)
}

// credits to https://www.regextester.com/96504
const URL_REGEX = /(([a-zA-Z]+):\/\/)(?:(?:[^\s()<>]+|\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))?\))+(?:\((?:[^\s()<>]+|(?:\(?:[^\s()<>]+\)))?\)|[^\s`!()[\]{};:'".,<>?«»“”‘’]))?/gi

/**
* Finds all urls in a string and wraps them in <a target="_blank" />
* @param {string} text
* @return {string}
*/
export function convertUrlsToHrefTags (text) {
return `${text || ''}`.replace(
URL_REGEX,
match => `<a href="${match}" target="_blank">${match}</a>`
)
}
70 changes: 70 additions & 0 deletions src/browser/components/clickable-urls.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2002-2019 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
* This file is part of Neo4j.
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import { convertUrlsToHrefTags } from './clickable-urls'

describe('clickable-urls', () => {
describe('convertUrlsToHrefTags', () => {
test('handles most common URL formats', () => {
const urls = [
'bolt://127.0.0.1:7687',
'http://foo.com',
'http://goo.gl',
'https://foo.com',
'https://www.foo.com',
'https://www.foo.com/',
'https://www.foo.com/bar',
'http://goo.gl/1',
'http://goo.gl/2',
'http://firstround.com/review/thoughts-on-gender-and-radical-candor/?ct=t(How_Does_Your_Leadership_Team_Rate_12_3_2015)',
'https://google.com',
'http://www.cool.com.au',
'http://www.cool.com.au/ersdfs',
'http://www.cool.com.au/ersdfs?dfd=dfgd@s=1',
'http://www.cool.com:81/index.html'
]
const expected = urls.map(
url => `<a href="${url}" target="_blank">${url}</a>`
)

expect(urls.map(convertUrlsToHrefTags)).toEqual(expected)
})

test('does not catch invalid or missing protocol urls', () => {
expect(convertUrlsToHrefTags('https:google.com')).toBe('https:google.com')
expect(convertUrlsToHrefTags('google.com')).toBe('google.com')
})

test('handles urls inside parentheses', () => {
expect(convertUrlsToHrefTags('(http://goo.gl/1)')).toBe(
'(<a href="http://goo.gl/1" target="_blank">http://goo.gl/1</a>)'
)
})

test('does not include puncuation, comma, exclamation', () => {
expect(convertUrlsToHrefTags('http://foo.com/.')).toBe(
'<a href="http://foo.com/" target="_blank">http://foo.com/</a>.'
)
expect(convertUrlsToHrefTags('http://foo.com/!')).toBe(
'<a href="http://foo.com/" target="_blank">http://foo.com/</a>!'
)
expect(convertUrlsToHrefTags('http://foo.com/,')).toBe(
'<a href="http://foo.com/" target="_blank">http://foo.com/</a>,'
)
})
})
})
3 changes: 2 additions & 1 deletion src/browser/modules/D3Visualization/components/Inspector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
} from './styled'
import { GrassEditor } from './GrassEditor'
import { RowExpandToggleComponent } from './RowExpandToggle'
import ClickableUrls from '../../../components/clickable-urls'

const mapItemProperties = itemProperties =>
itemProperties
Expand All @@ -51,7 +52,7 @@ const mapItemProperties = itemProperties =>
{prop.key + ': '}
</StyledInspectorFooterRowListKey>
<StyledInspectorFooterRowListValue className='value'>
{optionalToString(prop.value)}
<ClickableUrls text={optionalToString(prop.value)} />
</StyledInspectorFooterRowListValue>
</StyledInspectorFooterRowListPair>
))
Expand Down
15 changes: 13 additions & 2 deletions src/browser/modules/Stream/CypherFrame/TableView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import React, { Component } from 'react'
import { v4 } from 'uuid'
import { v1 as neo4j } from 'neo4j-driver'
import { sanitize } from 'dompurify'

import {
StyledStatsBar,
PaddedTableViewDiv,
Expand All @@ -41,6 +43,9 @@ import {
transformResultRecordsToResultArray
} from './helpers'
import { stringModifier } from 'services/bolt/cypherTypesFormatting'
import ClickableUrls, {
convertUrlsToHrefTags
} from '../../../components/clickable-urls'

const renderCell = entry => {
if (Array.isArray(entry)) {
Expand All @@ -54,14 +59,20 @@ const renderCell = entry => {
} else if (typeof entry === 'object') {
return renderObject(entry)
} else {
return stringifyMod(entry, stringModifier, true)
return <ClickableUrls text={stringifyMod(entry, stringModifier, true)} />
}
}
export const renderObject = entry => {
if (neo4j.isInt(entry)) return entry.toString()
if (entry === null) return <em>null</em>
return (
<StyledJsonPre>{stringifyMod(entry, stringModifier, true)}</StyledJsonPre>
<StyledJsonPre
dangerouslySetInnerHTML={{
__html: sanitize(
convertUrlsToHrefTags(stringifyMod(entry, stringModifier, true))
)
}}
/>
)
}
const buildData = entries => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ exports[`TableViews TableView does not display bodyMessage if rows 1`] = `
<td
class="table-properties DataTables__StyledTd-bf850j-3 jDsjVi"
>
"y"
<span>
"y"
</span>
</td>
</tr>
</tbody>
Expand All @@ -74,11 +76,15 @@ exports[`TableViews TableView does not display bodyMessage if rows 1`] = `
`;

exports[`TableViews TableView renderObject handles null values 1`] = `
<ForwardRef(DataTables__StyledJsonPre)>
{
"x": 1.0
}
</ForwardRef(DataTables__StyledJsonPre)>
<ForwardRef(DataTables__StyledJsonPre)
dangerouslySetInnerHTML={
Object {
"__html": "{
\\"x\\": 1.0
}",
}
}
/>
`;

exports[`TableViews TableView renderObject handles null values 2`] = `
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3506,6 +3506,11 @@ [email protected]:
dependencies:
domelementtype "1"

dompurify@^1.0.11:
version "1.0.11"
resolved "https://neo.jfrog.io/neo/api/npm/npm/dompurify/-/dompurify-1.0.11.tgz#fe0f4a40d147f7cebbe31a50a1357539cfc1eb4d"
integrity sha1-/g9KQNFH98674xpQoTV1Oc/B600=

[email protected]:
version "1.1.6"
resolved "https://neo.jfrog.io/neo/api/npm/npm/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485"
Expand Down