Skip to content

Commit ad59f3e

Browse files
Merge pull request #3010 from RedisInsight/fe/feature/RI-5376-parse-redis-url
#RI-5376 - update parsing redis url
2 parents 4141026 + 3406067 commit ad59f3e

File tree

9 files changed

+167
-85
lines changed

9 files changed

+167
-85
lines changed

redisinsight/desktop/src/lib/app/deep-link.handlers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const deepLinkHandler = async (from?: string): Promise<undefined | IParse
2222
break
2323
default:
2424
return {
25-
from: encodeURIComponent(from),
25+
from,
2626
target: url.query?.target || '_self',
2727
initialPage: url.query?.initialPage,
2828
} as IParsedDeepLink

redisinsight/ui/src/components/global-url-handler/GlobalUrlHandler.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ describe('GlobalUrlHandler', () => {
132132
password: 'password',
133133
port: 6379,
134134
tls: false,
135-
username: undefined,
135+
username: '',
136136
caCert: undefined,
137137
clientCert: undefined,
138138
}

redisinsight/ui/src/components/global-url-handler/GlobalUrlHandler.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { useHistory, useLocation } from 'react-router-dom'
22
import { useDispatch, useSelector } from 'react-redux'
33
import { useEffect } from 'react'
4-
import { ConnectionString } from 'connection-string'
54
import { isNull, isNumber, every, values, pick, some } from 'lodash'
6-
import { Pages, REDIS_URI_SCHEMES } from 'uiSrc/constants'
5+
import { Pages } from 'uiSrc/constants'
76
import { ADD_NEW_CA_CERT, ADD_NEW } from 'uiSrc/pages/home/constants'
87
import {
98
appRedirectionSelector,
@@ -16,7 +15,7 @@ import { userSettingsSelector } from 'uiSrc/slices/user/user-settings'
1615
import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling'
1716
import { autoCreateAndConnectToInstanceAction } from 'uiSrc/slices/instances/instances'
1817
import { getRedirectionPage } from 'uiSrc/utils/routing'
19-
import { Nullable, transformQueryParamsObject } from 'uiSrc/utils'
18+
import { Nullable, transformQueryParamsObject, parseRedisUrl } from 'uiSrc/utils'
2019

2120
const GlobalUrlHandler = () => {
2221
const { fromUrl } = useSelector(appRedirectionSelector)
@@ -60,12 +59,12 @@ const GlobalUrlHandler = () => {
6059
const from = params.get('from')
6160

6261
if (from) {
63-
dispatch(setFromUrl(decodeURIComponent(from)))
62+
dispatch(setFromUrl(from))
6463
history.replace({
6564
search: ''
6665
})
6766
}
68-
} catch (_e) {
67+
} catch {
6968
// do nothing
7069
}
7170
}, [search])
@@ -101,15 +100,14 @@ const GlobalUrlHandler = () => {
101100
)
102101
)
103102

104-
const url = new ConnectionString(redisUrl)
103+
const url = parseRedisUrl(redisUrl)
105104

106-
/* If a protocol exists, it should be a redis protocol */
107-
if (url.protocol && !REDIS_URI_SCHEMES.includes(url.protocol)) return
105+
if (!url) return
108106

109107
const obligatoryForAutoConnectFields = {
110-
host: url.hostname,
111-
port: url.port,
112-
username: url.user,
108+
host: url.host,
109+
port: url.port || 6379,
110+
username: url.username,
113111
password: url.password,
114112
}
115113

@@ -124,7 +122,7 @@ const GlobalUrlHandler = () => {
124122

125123
const db = {
126124
...obligatoryForAutoConnectFields,
127-
name: databaseAlias || url.host,
125+
name: databaseAlias || url.hostname || url.host,
128126
} as any
129127

130128
if (isAllObligatoryProvided && !isTlsProvided) {

redisinsight/ui/src/electron/components/ConfigElectron/ConfigElectron.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ const ConfigElectron = () => {
4747

4848
const deepLinkAction = (_e: any, url: IParsedDeepLink) => {
4949
if (url.from) {
50+
const fromUrl = encodeURIComponent(url.from)
5051
history.push({
51-
search: `from=${url.from}`
52+
search: `from=${fromUrl}`
5253
})
5354
}
5455
}

redisinsight/ui/src/pages/home/utils/form.tsx

Lines changed: 43 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { ConnectionString } from 'connection-string'
21
import { isUndefined, toString } from 'lodash'
32
import React from 'react'
43
import { FormikErrors } from 'formik'
5-
import { REDIS_URI_SCHEMES } from 'uiSrc/constants'
64
import { InstanceType } from 'uiSrc/slices/interfaces'
75
import {
86
ADD_NEW,
@@ -13,7 +11,7 @@ import {
1311
SshPassType
1412
} from 'uiSrc/pages/home/constants'
1513
import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces'
16-
import { Nullable } from 'uiSrc/utils'
14+
import { Nullable, parseRedisUrl } from 'uiSrc/utils'
1715

1816
export const getTlsSettings = (values: DbConnectionInfo) => ({
1917
useTls: values.tls,
@@ -100,7 +98,7 @@ export const applySSHDatabase = (database: any, values: DbConnectionInfo) => {
10098
database.ssh = true
10199
database.sshOptions = {
102100
host: sshHost,
103-
port: +sshPort,
101+
port: sshPort ? +sshPort : undefined,
104102
username: sshUsername,
105103
}
106104

@@ -201,74 +199,58 @@ export const autoFillFormDetails = (
201199
instanceType: InstanceType
202200
): boolean => {
203201
try {
204-
const details = new ConnectionString(content)
202+
const details = parseRedisUrl(content)
205203

206-
/* If a protocol exists, it should be a redis protocol */
207-
if (details.protocol && !REDIS_URI_SCHEMES.includes(details.protocol)) return false
208-
/*
209-
* Auto fill logic:
210-
* 1) If the port is parsed, we are sure that the user has indeed copied a connection string.
211-
* '172.18.0.2:12000' => {host: '172,18.0.2', port: 12000}
212-
* 'redis-12000.cluster.local:12000' => {host: 'redis-12000.cluster.local', port: 12000}
213-
* 'lorem ipsum' => {host: undefined, port: undefined}
214-
* 2) If the port is `undefined` but a redis URI scheme is present as protocol, we follow
215-
* the "Scheme semantics" as mentioned in the official URI schemes.
216-
* i) redis:// - https://www.iana.org/assignments/uri-schemes/prov/redis
217-
* ii) rediss:// - https://www.iana.org/assignments/uri-schemes/prov/rediss
218-
*/
219-
if (
220-
details.port !== undefined
221-
|| REDIS_URI_SCHEMES.includes(details.protocol || '')
222-
) {
223-
const getUpdatedInitialValues = () => {
224-
switch (instanceType) {
225-
case InstanceType.RedisEnterpriseCluster: {
226-
return ({
227-
host: details.hostname || initialValues.host || 'localhost',
228-
port: `${details.port || initialValues.port || 9443}`,
229-
username: details.user || '',
230-
password: details.password || '',
231-
})
232-
}
204+
if (!details) return false
205+
206+
const getUpdatedInitialValues = () => {
207+
switch (instanceType) {
208+
case InstanceType.RedisEnterpriseCluster: {
209+
return ({
210+
host: details.host || initialValues.host || 'localhost',
211+
port: `${details.port || initialValues.port || 9443}`,
212+
username: details.username || '',
213+
password: details.password || '',
214+
})
215+
}
233216

234-
case InstanceType.Sentinel: {
235-
return ({
236-
host: details.hostname || initialValues.host || 'localhost',
237-
port: `${details.port || initialValues.port || 9443}`,
238-
username: details.user || '',
239-
password: details.password,
240-
tls: details.protocol === 'rediss',
241-
})
242-
}
217+
case InstanceType.Sentinel: {
218+
return ({
219+
host: details.host || initialValues.host || 'localhost',
220+
port: `${details.port || initialValues.port || 9443}`,
221+
username: details.username || '',
222+
password: details.password,
223+
tls: details.protocol === 'rediss',
224+
})
225+
}
243226

244-
case InstanceType.Standalone: {
245-
return (getFormValues({
246-
name: details.host || initialValues.name || 'localhost:6379',
247-
host: details.hostname || initialValues.host || 'localhost',
248-
port: `${details.port || initialValues.port || 9443}`,
249-
username: details.user || '',
250-
password: details.password,
251-
tls: details.protocol === 'rediss',
252-
ssh: false,
253-
sshPassType: SshPassType.Password
254-
}))
255-
}
256-
default: {
257-
return {}
258-
}
227+
case InstanceType.Standalone: {
228+
return (getFormValues({
229+
name: details.hostname || initialValues.name || 'localhost:6379',
230+
host: details.host || initialValues.host || 'localhost',
231+
port: `${details.port || initialValues.port || 9443}`,
232+
username: details.username || '',
233+
password: details.password,
234+
tls: details.protocol === 'rediss',
235+
db: details.dbNumber,
236+
ssh: false,
237+
sshPassType: SshPassType.Password
238+
}))
239+
}
240+
default: {
241+
return {}
259242
}
260243
}
261-
setInitialValues(getUpdatedInitialValues())
262-
/*
244+
}
245+
setInitialValues(getUpdatedInitialValues())
246+
/*
263247
* autofill was successfull so return true
264248
*/
265-
return true
266-
}
249+
return true
267250
} catch (err) {
268251
/* The pasted content is not a connection URI so ignore. */
269252
return false
270253
}
271-
return false
272254
}
273255

274256
export const getSubmitButtonContent = (errors: FormikErrors<DbConnectionInfo>, submitIsDisabled?: boolean) => {

redisinsight/ui/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from './statuses'
88
export * from './instance'
99
export * from './apiResponse'
1010
export * from './parseResponse'
11+
export * from './parseRedisUrl'
1112
export * from './comparisons'
1213
export * from './longNames'
1314
export * from './cliHelper'
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Maybe, Nullable } from 'uiSrc/utils/types'
2+
3+
/*
4+
[redis[s]://] - Optional Protocol (redis or rediss)
5+
[username][:password]@ - Optional username and password
6+
host - Hostname or IP address
7+
[:port] - Optional port
8+
[/db-number] - Optional database number
9+
*/
10+
11+
interface ParsedRedisUrl {
12+
protocol: string
13+
username: string
14+
password: string
15+
host: string
16+
port: Maybe<number>
17+
hostname: Maybe<string>
18+
dbNumber: Maybe<number>
19+
}
20+
21+
const parseRedisUrl = (urlString: string = ''): Nullable<ParsedRedisUrl> => {
22+
const pureUrlPattern = /^([^:]+):(\d+)$/
23+
const pureMatch = urlString.match(pureUrlPattern)
24+
25+
if (pureMatch) {
26+
const [, host, port] = pureMatch
27+
return {
28+
protocol: 'redis',
29+
username: '',
30+
password: '',
31+
host,
32+
port: port ? parseInt(port, 10) : undefined,
33+
hostname: port ? `${host}:${port}` : host,
34+
dbNumber: undefined
35+
}
36+
}
37+
38+
// eslint-disable-next-line no-useless-escape
39+
const redisUrlPattern = /^(redis[s]?):\/\/(?:(.+)?@)?(?:.*@)?([^:\/]+)(?::(\d+))?(?:\/(\d+))?$/
40+
const match = urlString.match(redisUrlPattern)
41+
42+
if (!match) {
43+
return null
44+
}
45+
46+
const [, protocol, userInfo, host, port, dbNumber] = match
47+
const [, username, password] = userInfo?.match(/^(.*?)(?::(.*))?$/) || []
48+
49+
return {
50+
protocol: protocol || 'redis',
51+
username: username || '',
52+
password: password || '',
53+
host,
54+
port: port ? parseInt(port, 10) : undefined,
55+
hostname: port ? `${host}:${port}` : host,
56+
dbNumber: dbNumber ? parseInt(dbNumber, 10) : undefined
57+
}
58+
}
59+
60+
export {
61+
parseRedisUrl
62+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { parseRedisUrl } from 'uiSrc/utils/parseRedisUrl'
2+
3+
const defaultRedisParams = {
4+
protocol: 'redis',
5+
username: '',
6+
password: '',
7+
port: undefined,
8+
dbNumber: undefined
9+
}
10+
11+
const parseRedisUrlTests: Array<[string, any]> = [
12+
['http://user:pass@localhost:6380', null],
13+
['localhost', null],
14+
['localhost:6379', { ...defaultRedisParams, host: 'localhost', port: 6379, hostname: 'localhost:6379' }],
15+
['redis://localhost', { ...defaultRedisParams, host: 'localhost', hostname: 'localhost' }],
16+
['redis://:@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380' }],
17+
['redis://user:pass/@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', username: 'user', password: 'pass/' }],
18+
['redis://user:pa@ss@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', username: 'user', password: 'pa@ss' }],
19+
['redis://us@er:pa@ss@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', username: 'us@er', password: 'pa@ss' }],
20+
['redis://us@er:pa@:ss@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', username: 'us@er', password: 'pa@:ss' }],
21+
['redis://localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
22+
['redis://@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
23+
['redis://user@localhost:6380', { ...defaultRedisParams, username: 'user', host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
24+
['redis://:pass@localhost:6380', { ...defaultRedisParams, password: 'pass', host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
25+
['redis://user:pass@localhost:6380', { ...defaultRedisParams, username: 'user', password: 'pass', host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
26+
['rediss://user:pa%712ss@localhost:6380', { ...defaultRedisParams, protocol: 'rediss', username: 'user', password: 'pa%712ss', host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
27+
['rediss://d&@&21^$:pa%@7:12:[email protected]:6380', { ...defaultRedisParams, protocol: 'rediss', username: 'd&@&21^$', password: 'pa%@7:12:ss', host: 'local-host-123.net.com', port: 6380, hostname: 'local-host-123.net.com:6380', }],
28+
['rediss://user:pa%712ss@localhost:6380/2', { protocol: 'rediss', username: 'user', password: 'pa%712ss', host: 'localhost', port: 6380, hostname: 'localhost:6380', dbNumber: 2 }],
29+
]
30+
31+
describe('parseRedisUrl', () => {
32+
it.each(parseRedisUrlTests)('for input: %s (index), %s (shift), should be output: %s',
33+
(url, expected) => {
34+
const result = parseRedisUrl(url)
35+
expect(result).toEqual(expected)
36+
})
37+
})

0 commit comments

Comments
 (0)