Skip to content
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
2 changes: 1 addition & 1 deletion redisinsight/desktop/src/lib/app/deep-link.handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const deepLinkHandler = async (from?: string): Promise<undefined | IParse
break
default:
return {
from: encodeURIComponent(from),
from,
target: url.query?.target || '_self',
initialPage: url.query?.initialPage,
} as IParsedDeepLink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe('GlobalUrlHandler', () => {
password: 'password',
port: 6379,
tls: false,
username: undefined,
username: '',
caCert: undefined,
clientCert: undefined,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useHistory, useLocation } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { useEffect } from 'react'
import { ConnectionString } from 'connection-string'
import { isNull, isNumber, every, values, pick, some } from 'lodash'
import { Pages, REDIS_URI_SCHEMES } from 'uiSrc/constants'
import { Pages } from 'uiSrc/constants'
import { ADD_NEW_CA_CERT, ADD_NEW } from 'uiSrc/pages/home/constants'
import {
appRedirectionSelector,
Expand All @@ -16,7 +15,7 @@ import { userSettingsSelector } from 'uiSrc/slices/user/user-settings'
import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling'
import { autoCreateAndConnectToInstanceAction } from 'uiSrc/slices/instances/instances'
import { getRedirectionPage } from 'uiSrc/utils/routing'
import { Nullable, transformQueryParamsObject } from 'uiSrc/utils'
import { Nullable, transformQueryParamsObject, parseRedisUrl } from 'uiSrc/utils'

const GlobalUrlHandler = () => {
const { fromUrl } = useSelector(appRedirectionSelector)
Expand Down Expand Up @@ -60,12 +59,12 @@ const GlobalUrlHandler = () => {
const from = params.get('from')

if (from) {
dispatch(setFromUrl(decodeURIComponent(from)))
dispatch(setFromUrl(from))
history.replace({
search: ''
})
}
} catch (_e) {
} catch {
// do nothing
}
}, [search])
Expand Down Expand Up @@ -101,15 +100,14 @@ const GlobalUrlHandler = () => {
)
)

const url = new ConnectionString(redisUrl)
const url = parseRedisUrl(redisUrl)

/* If a protocol exists, it should be a redis protocol */
if (url.protocol && !REDIS_URI_SCHEMES.includes(url.protocol)) return
if (!url) return

const obligatoryForAutoConnectFields = {
host: url.hostname,
port: url.port,
username: url.user,
host: url.host,
port: url.port || 6379,
username: url.username,
password: url.password,
}

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

const db = {
...obligatoryForAutoConnectFields,
name: databaseAlias || url.host,
name: databaseAlias || url.hostname || url.host,
} as any

if (isAllObligatoryProvided && !isTlsProvided) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ const ConfigElectron = () => {

const deepLinkAction = (_e: any, url: IParsedDeepLink) => {
if (url.from) {
const fromUrl = encodeURIComponent(url.from)
history.push({
search: `from=${url.from}`
search: `from=${fromUrl}`
})
}
}
Expand Down
104 changes: 43 additions & 61 deletions redisinsight/ui/src/pages/home/utils/form.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { ConnectionString } from 'connection-string'
import { isUndefined, toString } from 'lodash'
import React from 'react'
import { FormikErrors } from 'formik'
import { REDIS_URI_SCHEMES } from 'uiSrc/constants'
import { InstanceType } from 'uiSrc/slices/interfaces'
import {
ADD_NEW,
Expand All @@ -13,7 +11,7 @@ import {
SshPassType
} from 'uiSrc/pages/home/constants'
import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces'
import { Nullable } from 'uiSrc/utils'
import { Nullable, parseRedisUrl } from 'uiSrc/utils'

export const getTlsSettings = (values: DbConnectionInfo) => ({
useTls: values.tls,
Expand Down Expand Up @@ -100,7 +98,7 @@ export const applySSHDatabase = (database: any, values: DbConnectionInfo) => {
database.ssh = true
database.sshOptions = {
host: sshHost,
port: +sshPort,
port: sshPort ? +sshPort : undefined,
username: sshUsername,
}

Expand Down Expand Up @@ -201,74 +199,58 @@ export const autoFillFormDetails = (
instanceType: InstanceType
): boolean => {
try {
const details = new ConnectionString(content)
const details = parseRedisUrl(content)

/* If a protocol exists, it should be a redis protocol */
if (details.protocol && !REDIS_URI_SCHEMES.includes(details.protocol)) return false
/*
* Auto fill logic:
* 1) If the port is parsed, we are sure that the user has indeed copied a connection string.
* '172.18.0.2:12000' => {host: '172,18.0.2', port: 12000}
* 'redis-12000.cluster.local:12000' => {host: 'redis-12000.cluster.local', port: 12000}
* 'lorem ipsum' => {host: undefined, port: undefined}
* 2) If the port is `undefined` but a redis URI scheme is present as protocol, we follow
* the "Scheme semantics" as mentioned in the official URI schemes.
* i) redis:// - https://www.iana.org/assignments/uri-schemes/prov/redis
* ii) rediss:// - https://www.iana.org/assignments/uri-schemes/prov/rediss
*/
if (
details.port !== undefined
|| REDIS_URI_SCHEMES.includes(details.protocol || '')
) {
const getUpdatedInitialValues = () => {
switch (instanceType) {
case InstanceType.RedisEnterpriseCluster: {
return ({
host: details.hostname || initialValues.host || 'localhost',
port: `${details.port || initialValues.port || 9443}`,
username: details.user || '',
password: details.password || '',
})
}
if (!details) return false

const getUpdatedInitialValues = () => {
switch (instanceType) {
case InstanceType.RedisEnterpriseCluster: {
return ({
host: details.host || initialValues.host || 'localhost',
port: `${details.port || initialValues.port || 9443}`,
username: details.username || '',
password: details.password || '',
})
}

case InstanceType.Sentinel: {
return ({
host: details.hostname || initialValues.host || 'localhost',
port: `${details.port || initialValues.port || 9443}`,
username: details.user || '',
password: details.password,
tls: details.protocol === 'rediss',
})
}
case InstanceType.Sentinel: {
return ({
host: details.host || initialValues.host || 'localhost',
port: `${details.port || initialValues.port || 9443}`,
username: details.username || '',
password: details.password,
tls: details.protocol === 'rediss',
})
}

case InstanceType.Standalone: {
return (getFormValues({
name: details.host || initialValues.name || 'localhost:6379',
host: details.hostname || initialValues.host || 'localhost',
port: `${details.port || initialValues.port || 9443}`,
username: details.user || '',
password: details.password,
tls: details.protocol === 'rediss',
ssh: false,
sshPassType: SshPassType.Password
}))
}
default: {
return {}
}
case InstanceType.Standalone: {
return (getFormValues({
name: details.hostname || initialValues.name || 'localhost:6379',
host: details.host || initialValues.host || 'localhost',
port: `${details.port || initialValues.port || 9443}`,
username: details.username || '',
password: details.password,
tls: details.protocol === 'rediss',
db: details.dbNumber,
ssh: false,
sshPassType: SshPassType.Password
}))
}
default: {
return {}
}
}
setInitialValues(getUpdatedInitialValues())
/*
}
setInitialValues(getUpdatedInitialValues())
/*
* autofill was successfull so return true
*/
return true
}
return true
} catch (err) {
/* The pasted content is not a connection URI so ignore. */
return false
}
return false
}

export const getSubmitButtonContent = (errors: FormikErrors<DbConnectionInfo>, submitIsDisabled?: boolean) => {
Expand Down
1 change: 1 addition & 0 deletions redisinsight/ui/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './statuses'
export * from './instance'
export * from './apiResponse'
export * from './parseResponse'
export * from './parseRedisUrl'
export * from './comparisons'
export * from './longNames'
export * from './cliHelper'
Expand Down
62 changes: 62 additions & 0 deletions redisinsight/ui/src/utils/parseRedisUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Maybe, Nullable } from 'uiSrc/utils/types'

/*
[redis[s]://] - Optional Protocol (redis or rediss)
[username][:password]@ - Optional username and password
host - Hostname or IP address
[:port] - Optional port
[/db-number] - Optional database number
*/

interface ParsedRedisUrl {
protocol: string
username: string
password: string
host: string
port: Maybe<number>
hostname: Maybe<string>
dbNumber: Maybe<number>
}

const parseRedisUrl = (urlString: string = ''): Nullable<ParsedRedisUrl> => {
const pureUrlPattern = /^([^:]+):(\d+)$/
const pureMatch = urlString.match(pureUrlPattern)

if (pureMatch) {
const [, host, port] = pureMatch
return {
protocol: 'redis',
username: '',
password: '',
host,
port: port ? parseInt(port, 10) : undefined,
hostname: port ? `${host}:${port}` : host,
dbNumber: undefined
}
}

// eslint-disable-next-line no-useless-escape
const redisUrlPattern = /^(redis[s]?):\/\/(?:(.+)?@)?(?:.*@)?([^:\/]+)(?::(\d+))?(?:\/(\d+))?$/
const match = urlString.match(redisUrlPattern)

if (!match) {
return null
}

const [, protocol, userInfo, host, port, dbNumber] = match
const [, username, password] = userInfo?.match(/^(.*?)(?::(.*))?$/) || []

return {
protocol: protocol || 'redis',
username: username || '',
password: password || '',
host,
port: port ? parseInt(port, 10) : undefined,
hostname: port ? `${host}:${port}` : host,
dbNumber: dbNumber ? parseInt(dbNumber, 10) : undefined
}
}

export {
parseRedisUrl
}
37 changes: 37 additions & 0 deletions redisinsight/ui/src/utils/tests/parseRedisUrl.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { parseRedisUrl } from 'uiSrc/utils/parseRedisUrl'

const defaultRedisParams = {
protocol: 'redis',
username: '',
password: '',
port: undefined,
dbNumber: undefined
}

const parseRedisUrlTests: Array<[string, any]> = [
['http://user:pass@localhost:6380', null],
['localhost', null],
['localhost:6379', { ...defaultRedisParams, host: 'localhost', port: 6379, hostname: 'localhost:6379' }],
['redis://localhost', { ...defaultRedisParams, host: 'localhost', hostname: 'localhost' }],
['redis://:@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380' }],
['redis://user:pass/@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', username: 'user', password: 'pass/' }],
['redis://user:pa@ss@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', username: 'user', password: 'pa@ss' }],
['redis://us@er:pa@ss@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', username: 'us@er', password: 'pa@ss' }],
['redis://us@er:pa@:ss@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', username: 'us@er', password: 'pa@:ss' }],
['redis://localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
['redis://@localhost:6380', { ...defaultRedisParams, host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
['redis://user@localhost:6380', { ...defaultRedisParams, username: 'user', host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
['redis://:pass@localhost:6380', { ...defaultRedisParams, password: 'pass', host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
['redis://user:pass@localhost:6380', { ...defaultRedisParams, username: 'user', password: 'pass', host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
['rediss://user:pa%712ss@localhost:6380', { ...defaultRedisParams, protocol: 'rediss', username: 'user', password: 'pa%712ss', host: 'localhost', port: 6380, hostname: 'localhost:6380', }],
['rediss://d&@&21^$:pa%@7:12:ss@local-host-123.net.com: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', }],
['rediss://user:pa%712ss@localhost:6380/2', { protocol: 'rediss', username: 'user', password: 'pa%712ss', host: 'localhost', port: 6380, hostname: 'localhost:6380', dbNumber: 2 }],
]

describe('parseRedisUrl', () => {
it.each(parseRedisUrlTests)('for input: %s (index), %s (shift), should be output: %s',
(url, expected) => {
const result = parseRedisUrl(url)
expect(result).toEqual(expected)
})
})
Loading