Skip to content

Commit c5c8ca0

Browse files
committed
Fix issue with creating unique keys from user input
Encode and unescape before base64 encoding. Added tests for Settings.js and for the utility function toKeyString to demonstrate this.
1 parent 2846de2 commit c5c8ca0

File tree

7 files changed

+159
-19
lines changed

7 files changed

+159
-19
lines changed

src/browser/components/Tables.jsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
* You should have received a copy of the GNU General Public License
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
20-
/* global btoa */
2120
import React from 'react'
2221
import styled from 'styled-components'
22+
import { toKeyString } from 'services/utils'
2323

2424
const StyledTable = styled.table`
2525
border-radius: 4px;
@@ -42,8 +42,12 @@ const StyledTh = styled.th`
4242
border-color: #ddd;
4343
padding: 10px 15px;
4444
`
45-
const StyledTd = styled.td`padding: 5px;`
46-
const StyledTdKey = styled(StyledTd)`font-weight: bold;`
45+
const StyledTd = styled.td`
46+
padding: 5px;
47+
`
48+
const StyledTdKey = styled(StyledTd)`
49+
font-weight: bold;
50+
`
4751
export const SysInfoTableContainer = styled.div`
4852
display: flex;
4953
flex-flow: row wrap;
@@ -80,7 +84,7 @@ export const SysInfoTableEntry = ({
8084
const mappedValue = getValue(value, mapper)
8185
const val = mappedValue || missingValuePlaceholder
8286
return mappedValue || !optional ? (
83-
<StyledTdKey key={btoa(val)}>{val}</StyledTdKey>
87+
<StyledTdKey key={toKeyString(val)}>{val}</StyledTdKey>
8488
) : null
8589
})}
8690
</StyledTr>
@@ -93,7 +97,7 @@ export const SysInfoTableEntry = ({
9397
const mappedValue = getValue(value, mapper)
9498
const val = mappedValue || missingValuePlaceholder
9599
return mappedValue || !optional ? (
96-
<StyledTd key={btoa(val)}>{val}</StyledTd>
100+
<StyledTd key={toKeyString(val)}>{val}</StyledTd>
97101
) : null
98102
})}
99103
</StyledTr>

src/browser/modules/Sidebar/Favorites.jsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
2020

21-
/* global btoa */
2221
import React, { Component } from 'react'
2322
import { connect } from 'react-redux'
2423
import { withBus } from 'react-suber'
@@ -27,6 +26,7 @@ import * as favorite from 'shared/modules/favorites/favoritesDuck'
2726
import * as folder from 'shared/modules/favorites/foldersDuck'
2827
import { getSettings } from 'shared/modules/settings/settingsDuck'
2928
import { executeCommand } from 'shared/modules/commands/commandsDuck'
29+
import { toKeyString } from 'services/utils'
3030

3131
import Render from 'browser-components/Render'
3232
import Favorite from './Favorite'
@@ -48,7 +48,7 @@ const mapFavorites = (favorites, props, isChild, moveAction) => {
4848
return (
4949
<Favorite
5050
entry={entry}
51-
key={`${btoa(entry.name + entry.content + entry.id)}`}
51+
key={`${toKeyString(entry.name + entry.content + entry.id)}`}
5252
id={entry.id}
5353
name={entry.name}
5454
content={entry.content}
@@ -257,5 +257,10 @@ const mapDispatchToProps = (dispatch, ownProps) => {
257257
}
258258
}
259259
export default DragDropContext(HTML5Backend)(
260-
withBus(connect(mapStateToProps, mapDispatchToProps)(Favorites))
260+
withBus(
261+
connect(
262+
mapStateToProps,
263+
mapDispatchToProps
264+
)(Favorites)
265+
)
261266
)

src/browser/modules/Sidebar/Settings.jsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
* You should have received a copy of the GNU General Public License
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
20-
/* global btoa */
2120
import React from 'react'
2221
import { connect } from 'react-redux'
2322
import * as actions from 'shared/modules/settings/settingsDuck'
@@ -35,6 +34,7 @@ import {
3534
StyledSettingLabel,
3635
StyledSettingTextInput
3736
} from './styled'
37+
import { toKeyString } from 'services/utils'
3838

3939
const visualSettings = [
4040
{
@@ -140,20 +140,24 @@ const visualSettings = [
140140
}
141141
]
142142

143-
export const Settings = ({ settings, onSettingsSave = () => {} }) => {
143+
export const Settings = ({
144+
settings,
145+
visualSettings,
146+
onSettingsSave = () => {}
147+
}) => {
144148
if (!settings) return null
145-
const mappedSettings = visualSettings.map((visualSetting, oi) => {
149+
const mappedSettings = visualSettings.map(visualSetting => {
146150
const title = <DrawerSubHeader>{visualSetting.title}</DrawerSubHeader>
147151
const mapSettings = visualSetting.settings
148-
.map((settingObj, i) => {
152+
.map(settingObj => {
149153
const setting = Object.keys(settingObj)[0]
150154
if (typeof settings[setting] === 'undefined') return false
151155
const visual = settingObj[setting].displayName
152156
const tooltip = settingObj[setting].tooltip || ''
153157

154158
if (!settingObj[setting].type || settingObj[setting].type === 'input') {
155159
return (
156-
<StyledSetting key={btoa(visual)}>
160+
<StyledSetting key={toKeyString(visual)}>
157161
<StyledSettingLabel title={tooltip}>{visual}</StyledSettingLabel>
158162
<StyledSettingTextInput
159163
onChange={event => {
@@ -168,7 +172,7 @@ export const Settings = ({ settings, onSettingsSave = () => {} }) => {
168172
)
169173
} else if (settingObj[setting].type === 'radio') {
170174
return (
171-
<StyledSetting key={btoa(visual)}>
175+
<StyledSetting key={toKeyString(visual)}>
172176
<StyledSettingLabel title={tooltip}>{visual}</StyledSettingLabel>
173177
<RadioSelector
174178
options={settingObj[setting].options}
@@ -182,7 +186,7 @@ export const Settings = ({ settings, onSettingsSave = () => {} }) => {
182186
)
183187
} else if (settingObj[setting].type === 'checkbox') {
184188
return (
185-
<StyledSetting key={btoa(visual)}>
189+
<StyledSetting key={toKeyString(visual)}>
186190
<CheckboxSelector
187191
onChange={event => {
188192
settings[setting] = event.target.checked
@@ -197,7 +201,7 @@ export const Settings = ({ settings, onSettingsSave = () => {} }) => {
197201
})
198202
.filter(setting => setting !== false)
199203
return (
200-
<React.Fragment key={btoa(visualSetting.title)}>
204+
<React.Fragment key={toKeyString(visualSetting.title)}>
201205
{title}
202206
{mapSettings}
203207
</React.Fragment>
@@ -218,7 +222,8 @@ export const Settings = ({ settings, onSettingsSave = () => {} }) => {
218222

219223
const mapStateToProps = state => {
220224
return {
221-
settings: state.settings
225+
settings: state.settings,
226+
visualSettings
222227
}
223228
}
224229

@@ -230,4 +235,7 @@ const mapDispatchToProps = dispatch => {
230235
}
231236
}
232237

233-
export default connect(mapStateToProps, mapDispatchToProps)(Settings)
238+
export default connect(
239+
mapStateToProps,
240+
mapDispatchToProps
241+
)(Settings)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2002-2018 "Neo4j, Inc"
3+
* Network Engine for Objects in Lund AB [http://neotechnology.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
/* global describe, beforeEach, afterEach, test, expect */
22+
import React from 'react'
23+
import { render, cleanup } from 'react-testing-library'
24+
import { Settings } from './Settings'
25+
26+
afterEach(cleanup)
27+
28+
test('Settings renders with strange characters in display name', () => {
29+
// Given
30+
const settings = { testSetting: true }
31+
const visualSettings = [
32+
{
33+
title: 'Test åäö settings',
34+
settings: [
35+
{
36+
testSetting: {
37+
displayName: 'åäö üüü'
38+
}
39+
}
40+
]
41+
}
42+
]
43+
44+
// When
45+
const { container } = render(
46+
<Settings settings={settings} visualSettings={visualSettings} />
47+
)
48+
49+
// Then
50+
expect(container).toMatchSnapshot()
51+
})
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Settings renders with strange characters in display name 1`] = `
4+
<div>
5+
<div
6+
class="sc-bdVaJa fFGtIw"
7+
id="db-settings"
8+
>
9+
<h4
10+
class="sc-bwzfXH gJZKwg"
11+
>
12+
Browser Settings
13+
</h4>
14+
<div
15+
class="sc-bZQynM hYejcx"
16+
>
17+
<div
18+
class="sc-ifAKCX jDKYZc"
19+
>
20+
<div
21+
class="sc-EHOje fyzZhj"
22+
>
23+
<h5
24+
class="sc-bxivhb jqgGlu"
25+
>
26+
Test åäö settings
27+
</h5>
28+
<div
29+
class="sc-VigVT kULqyn"
30+
>
31+
<div
32+
class="sc-jTzLTM cjMpzP"
33+
title=""
34+
>
35+
åäö üüü
36+
</div>
37+
<input
38+
class="testSetting sc-fjdhpX hFlOzk"
39+
title=""
40+
value="true"
41+
/>
42+
</div>
43+
</div>
44+
</div>
45+
</div>
46+
</div>
47+
</div>
48+
`;

src/shared/services/utils.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* You should have received a copy of the GNU General Public License
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
20+
/* global btoa */
21+
2022
import parseUrl from 'url-parse'
2123

2224
export const deepEquals = (x, y) => {
@@ -258,7 +260,9 @@ export const stringifyMod = (
258260
) => {
259261
prettyLevel = !prettyLevel
260262
? false
261-
: prettyLevel === true ? 1 : parseInt(prettyLevel)
263+
: prettyLevel === true
264+
? 1
265+
: parseInt(prettyLevel)
262266
const nextPrettyLevel = prettyLevel ? prettyLevel + 1 : false
263267
const newLine = prettyLevel ? '\n' : ''
264268
const indentation =
@@ -395,3 +399,5 @@ export const optionalToString = v =>
395399
![null, undefined].includes(v) && typeof v.toString === 'function'
396400
? v.toString()
397401
: v
402+
403+
export const toKeyString = str => btoa(encodeURIComponent(str))

src/shared/services/utils.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,21 @@ describe('Object props manipulation', () => {
520520
})
521521
})
522522
})
523+
describe('toKeyString', () => {
524+
it('can encode strings with special characters', () => {
525+
// Given
526+
const strs = [
527+
{ str: 'hey ho ', expect: 'aGV5JTIwaG8lMjAlRUYlQTMlQkY=' },
528+
{
529+
str: '✓ à la mode',
530+
expect: 'JUUyJTlDJTkzJTIwJUMzJUEwJTIwbGElMjBtb2Rl'
531+
},
532+
{ str: '😍', expect: 'JUYwJTlGJTk4JThE' }
533+
]
534+
535+
// When & Then
536+
strs.forEach(str => {
537+
expect(utils.toKeyString(str.str)).toEqual(str.expect)
538+
})
539+
})
540+
})

0 commit comments

Comments
 (0)